Building To Improvement Linkages

LeeS

Imperator
Joined
Jul 23, 2013
Messages
7,241
Location
Illinois, USA
Linking City Buildings to Improvements Near Cities: A Handler

Introduction (Goals, etc):
You can skip to Downloading and Adding the Handler to Your Mod if you want to. But before asking questions about why the Handler is not doing 'X' or 'Y', you need to examine this section of the thread because the answer is almost certainly included in the Introductory discussion on Goals and Overall Concept.
  1. I've seen the issue of linking a city building to a terrain-improvement near the city coming up a bit recently on the forum.
    • This issue arises because Firaxis did not provide us a table such as <Building_ImprovementYieldChanges>, so in order to implement an affect that requires first that a city has a specified building, and second that there be some number of plot-improvements near the city, some form of lua is required.
    • Firaxis did provide a <Building_ResourceYieldChanges> so there is no need to concern ourselves with cases where this pre-provided game-table can be used.
    • We are concerned, therefore, with cases where a mod-maker wants to implement an effect based on how many of a particular improvement exist near a city.
    • The improvement might have a resource on the same tile (ie, Mines), or it might not. Also, as with Mines or Pastures or Camps, the improvement might be buildable on numerous different resources, whereas a mod-maker might only be interested in Pastures that are built on Sheep tiles, and does not want to 'count' Pastures that are on Cows or Horses.
  2. To deal with this issue of no pre-provided game-table, what I've seen a lot of is lua constructions like these:
    Spoiler :
    Code:
    function Something(iPlayer)
    	local pPlayer = Players[iPlayer];
    	for pCity in pPlayer:Cities() do
    		if pCity:IsHasBuilding(iBuildingSomething) then
    			local iNumImproves = 0
    			--then some method to scan through all a city's plots and grab the count of all the improvements that are relevant
    			if iNumImproves > 6 then
    				iNumImproves = 6
    			end
    			pCity:SetNumRealBuilding(iDummyBuildingSomething, iNumImproves)
    		else
    			pCity:SetNumRealBuilding(iDummyBuildingSomething, 0)
    		end
    	end
    end
    GameEvents.PlayerDoTurn.Add(Something)
    
    function SomethingElse(iPlayer)
    	local pPlayer = Players[iPlayer];
    	for pCity in pPlayer:Cities() do
    		if pCity:IsHasBuilding(iBuildingSomethingElse) then
    			local iNumImproves = 0
    			--then some method to scan through all a city's plots and grab the count of all the improvements that are relevant
    			if iNumImproves > 2 then
    				iNumImproves = 2
    			end
    			pCity:SetNumRealBuilding(iDummyBuildingSomethingElse, iNumImproves)
    		else
    			pCity:SetNumRealBuilding(iDummyBuildingSomethingElse, 0)
    		end
    	end
    end
    GameEvents.PlayerDoTurn.Add(SomethingElse)
    • There is nothing wrong with this approach per se
    • If we think about it, though, if we have six of these stand-alone PlayerDoTurn events within the same lua script then potentially every plot near every player's cities are being scanned up to six times each and every turn. It gets worse on larger maps where each player can potentially have 20-30 or more cities, especially where other mods are running that remove many of the happiness or social-policy hits to actually building empires that can stand the test of time.
    • What we need is a method to gather all these disparate functions into one master function so that no city and its plots is ever handled more than once per turn. Obviously if one uses a DLL mod that allows this as a direct API event or as a direct xml-table, this methodology will not be of use. Currently, VMC does not have a direct-linkage system for Building-to-Improvement such as an xml-table like "Building_ImprovementYieldChanges" -- CP I am unsure of.
  3. So the goals of this handler are:
    • Create a method that only scans through the plots surrounding a single city once per turn as part of PlayerDoTurn events processing.
      1. Also, we want the CityConstructed event to be included under the correct circumstances (ie, the player bought a building rather than finished constructing it). Buildings that are 'constructed' as opposed to 'purchased' are handled during turn processing anyway, and this occurs before PlayerDoTurn events, so it is unnecessary overhead to execute a CityConstructed event when a city finishes 'constructing' as opposed to 'buying' a building.
      2. We could also add one of the Improvemnent Is Completed events, such as BuildFinished, but doing so is problematical in that the only reliable method to use is to run through all of a player's cities when this Improvement Completion occurs, otherwise we will not in all cases get reliable data depending on the conditions we wish to establish for our 'counting' methods. Improvement Is Completed events only run in one of five circumstances:
        • The player presses the 'build action' button for a worker (or other unit that can 'build' an improvement) that will complete the improvement on that turn anyway.
        • Workboats are consumed in creating a Fishing Boat or Offshore Well.
        • The player uses a Great Person to create an improvment
        • The player uses an Archeologist to create a 'landmark'
        • Normal processing of worker 'build' completion just before the end of the player's turn (pressing 'NEXT TURN' causes all workers and other units that can 'build' an improvement to be updated in the progress of the improvement 'build' before the turn processes to the next player).
        In all of these cases, we run into the potential issues of incorrect data being implemented unless we also run through all of a player's cities every time an Improvmeent is completed. In the case of the processing that occurs at the endof the player's turn, this means every city must be run-through for every improvement we are interested in anywhere such an improvement is completed by that player, which in the later stages of the game can be a huge amount of processing, all of which we are going to repeat and essentially negate during PlayerDoTurn processing. Even if we use a method to determine which city should be processed for each improvement that was completed, the results are going to be prone to error as compared to the reaults obtained from PlayerDoTurn processing, and then we are going to do it all over again and negate all these implementations anyway when PlayerDoTurn executes.
    • This method needs to gather up all the data we need for each city regarding the improvements we are interested in, and how many of which improvement are around each city.
      • In doing this it needs configurability for choices such as
        1. a maximum number of a specific improvement that are allowed to affect any one city
        2. whether or not 1 copy of an Improvement = 1 Effect of some kind
        3. whether or not the tile must be worked
        4. whether the same tile can be counted more than once (ie, can overlapping cities 'use' the same tile, even if they aren't all working the tile)
        5. whether or not the player must own the tile
        6. whether or not a specified resource needs to be on the tile
        7. whether or not tiles with any resource should be ignored
        8. whether or not a pillaged tile should be counted
    • This method needs to handle all the 'real' buildings that a city has which need linkage to surrounding tile improvements.
    • This method needs to handle implementation of all 'dummy' buildings that ought to be set within a city based on the data gathered from the scan of plots around each individual city.
    • This method needs to also allow us "special handling" capability if we do not want to add 'dummy' buildings to a city based on how many of a particular improvement are near the city.
    • This method needs to provide for configuration of between 1 to as many as needed pairs of Building-To-Improvement linkages.
  4. The handler code that is linked in this thread does all these things. For now, it is added as an attachment to this post.

Downloading and Adding the Handler to Your Mod:
  1. The handler is downloadable from here: "City Building To Terrain Improvement Linkages" Download (CivFanatics Download Database link)
  2. The Download is in the form of a civ5mod file, so just as with any other such file, either extract it manually or copy/paste it into the game's MODS folder and let the game extract it for you.
  3. The handler as packaged is a fully functioning mod which you can use to experiment with if desired. It has three dummy buildings included that will be added to player cities when the city has a Granary, Workshop, or Longhouse, and the correct improvements are also present near the city.
  4. The file CityBuildingsToNearbyImprovementsHandler.lua within the LUA folder is the only file you actually require from within the downloadable handler mod.
  5. Add CityBuildingsToNearbyImprovementsHandler.lua to your mod, and set it up as an InGameUIAddin in the 'Content' tab of ModBuddy. See Post #3 of whoward69's what ModBuddy setting for what file types tutorial
  6. You do not need nor actually want the SampleDummyBuildings.xml file as part of your mod. You want to define your own dummy buildings, and add their XML 'Type' names in the correct places within the text of file CityBuildingsToNearbyImprovementsHandler.lua
  7. It is probably better not to add additional lua-code that your mod needs to file CityBuildingsToNearbyImprovementsHandler.lua, such as a CityConstructed event that needs to run when a custom World Wonder is constructed by a player.

Configuring the Handler as Required by Your Mod:
  1. Open the file CityBuildingsToNearbyImprovementsHandler.lua and you will see this chunk of code:
    Spoiler :
    Code:
    local tRealBuildingData = {}	--don't change this line
    --------------------------------------------------------------
    --[[
    
    USER CONFIGURABLE VARIABLES
    
    	Make changes to these variables as desired and needed
    
    ]]--
    --------------------------------------------------------------
    
    local bRequiresSpecificCiv = false
    local iSpecificRequiredCiv = GameInfoTypes["CIVILIZATION_AMERICA"]
    local bDoNotApplyToMinorCivs = true
    
    
    
    
    ----------------------------------------------------------------
    --enter data into table tRealBuildingData here
    
    --each subtable with table 'Counters' for a 'Real' building MUST have a designation of 'DummyBuilding=XXXXX'
    ----------------------------------------------------------------
    
    
    tRealBuildingData[GameInfoTypes.BUILDING_GRANARY] = {
    Counters={
    {DummyBuilding=GameInfoTypes["BUILDING_GRANARY_WHEAT_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_FARM, ResourceType=GameInfoTypes.RESOURCE_WHEAT, LimitPerCity=4, PlotsPerEffect=1},
    {DummyBuilding=GameInfoTypes["BUILDING_GRANARY_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_CAMP, ResourceType=GameInfoTypes.RESOURCE_DEER, MustBeWorked=false, LimitPerCity=2, PlotsPerEffect=1},
    {DummyBuilding=GameInfoTypes["BUILDING_GRANARY_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_FARM, MustBeWorked=false, DoNotCountResourceTiles=true, LimitPerCity=2, PlotsPerEffect=1}
    } }
    tRealBuildingData[GameInfoTypes.BUILDING_WORKSHOP] = {ApplyToAllInClass=true,
    Counters={ {DummyBuilding=GameInfoTypes["BUILDING_WORKSHOP_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, LimitPerCity=-1 },
    {DummyBuilding="LUMBERMILL_TOTAL", ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, MustBeOwned=false, MustBeWorked=false, LimitPerCity=-1 },
    {DummyBuilding="LUMBERMILL_OWNED", ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, MustBeOwned=true, MustBeWorked=false, LimitPerCity=-1 }
    } }
    tRealBuildingData[GameInfoTypes.BUILDING_WORKSHOP].SpecialHandling = (
    	function(BuildingID, tDummyQuantitiesTable, pCity, iPlayer)
    		local pPlayer = Players[iPlayer]
    		if not pPlayer:IsHuman() then return end
    		local iDummyBuilding = GameInfoTypes["BUILDING_WORKSHOP_DUMMY"]
    		local iOwnedLumbermills = tDummyQuantitiesTable["LUMBERMILL_OWNED"]
    		local iTotalLumbermills = tDummyQuantitiesTable["LUMBERMILL_TOTAL"]
    		local iNumberDummyToAdd = tDummyQuantitiesTable[iDummyBuilding]
    		local sDummyBuildingName = Locale.ConvertTextKey(GameInfo.Buildings[iDummyBuilding].Description)
    		print("The total number of Lumbermills within the working range of " .. pCity:GetName() .. " is " .. iTotalLumbermills)
    		print("The total number of Lumbermills within the working range of " .. pCity:GetName() .. " that are owned by player " .. pPlayer:GetName() .. " is " .. iOwnedLumbermills)
    		print("The total number of Dummy Buildings " .. sDummyBuildingName .. " that should be added if " .. pCity:GetName() .. " has a Workshop-Class Building is " .. iNumberDummyToAdd)
    		if pCity:IsHasBuilding(BuildingID) then
    			pCity:SetNumRealBuilding(iDummyBuilding, iNumberDummyToAdd)
    		else pCity:SetNumRealBuilding(iDummyBuilding, 0)
    		end
    	end
    	)
    This is the only place where you ever need to make changes to the code within the Handler's lua.​
    • Don't fret if this looks like Sandskrit writing encoded in Ancient Greek translated into Demotic Egyptian by a Mayan Numismatist. We'll be going through the methods you need to configure this to your needs for your mod.
    • The exact chunk of code used is included as a reference on how the code ought to look to get certain specified 'counting' and 'implementation' done by the handler. First I'll go through what that code is doing, and then I will present instruction on how you 'build' your own set of instructions needed by your mod.
  2. This line
    Code:
    local tRealBuildingData = {}	--don't change this line
    creates the lua-table the rest of the handler needs to implement the counting actions and the adding of dummy buildings or other instructions you give. The bulk of your instructions to the handler are done through this lua-table. As it says right there on that line, never ever under any circumstances channge this line
  3. In case you are somewhat new to lua, this chunk of code
    Code:
    --[[
    
    USER CONFIGURABLE VARIABLES
    
    	Make changes to these variables as desired and needed
    
    ]]--
    is another way to format user-readable comments within an lua-script as opposed to double-dashes ("--")
  4. We then have three variables we can configure:
    Code:
    local bRequiresSpecificCiv = false
    local iSpecificRequiredCiv = GameInfoTypes["CIVILIZATION_AMERICA"]
    local bDoNotApplyToMinorCivs = true
    You should not remove these variables from the code, but you can change their values:
    • bRequiresSpecificCiv is used to specify whether the code should run for everyone or only a specific civilization. You also have to specify the civilization as variable iSpecificRequiredCiv.
      • true means that only a specified civilization will be processed by the code, and then only when the specified civilization is one of the players (human or AI) in the current game.
      • false means all players will be processed, except that City-States are controlled by the variable bDoNotApplyToMinorCivs
    • iSpecificRequiredCiv is used to specify which civilization is the one the code will execute for when variable bRequiresSpecificCiv is set to true. If variable bRequiresSpecificCiv is set to false, then it does not matter what is specified for variable iSpecificRequiredCiv: you can just leave it set as it is, "CIVILIZATION_AMERICA".
    • bDoNotApplyToMinorCivs is used to specify whether the code should execute the counting of improvements and the effects from those counts for City-States.
      • true means the City-States will not be processed
      • false means the City-States will be processed
  5. The rest of the quoted code is a possible construction of table tRealBuildingData to enter into it all the counts that are needed and the 'counting-requirements' that are desired.
  6. The primary structure of lua table tRealBuildingData's "key" and "value" pairs is that each such primary pair must be defined so that the "key" is a valid building ID# from within the game's <Buildings> table, and the "value" side of the pair is defined as a sub-table within the larger table tRealBuildingData.
  7. In the example code, the first such "key,value" pair assigned to table tRealBuildingData is
    Code:
    tRealBuildingData[color="blue"][b][GameInfoTypes.BUILDING_GRANARY] = {[/b][/color]
    Counters={
    {DummyBuilding=GameInfoTypes["BUILDING_GRANARY_WHEAT_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_FARM, ResourceType=GameInfoTypes.RESOURCE_WHEAT, LimitPerCity=4, PlotsPerEffect=1},
    {DummyBuilding=GameInfoTypes["BUILDING_GRANARY_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_CAMP, ResourceType=GameInfoTypes.RESOURCE_DEER, MustBeWorked=false, LimitPerCity=2, PlotsPerEffect=1},
    {DummyBuilding=GameInfoTypes["BUILDING_GRANARY_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_FARM, MustBeWorked=false, DoNotCountResourceTiles=true, LimitPerCity=2, PlotsPerEffect=1}
    } [color="blue"][b]}[/b][/color]
    • If we take out all the data within the "value" subtable, and only look at the parts highlighted in blue above, we have
      Code:
      tRealBuildingData[color="blue"][b][GameInfoTypes.BUILDING_GRANARY][/b][/color] = [color="green"][b]{ }[/b][/color]
      • The blue part is the "key" side of the "key,value" pair
      • The green part is the "value" side of the "key,value" pair
    • All of the primary "keys" assigned to lua table tRealBuildingData must hold a valid ID# for a building listed within table <Buildings>, and cannot be anything else. The rest of code is organized to look for whether or not a particular city has this building or not in order to determine whether counting needs to proceed for that type of building for that city.
    • The building ID# listed as a primary "key" will usually be a building that a player normally constructs in their cities, or that they can normally buy in their cities. But you can also use Building ID#s for buildings that will only be added to a city as a result of some lua-method, such as an Event or a Decision, or a building that comes from a Leader Trait "FreeBuilding", as a few examples.
      • Note that adding a building to a city via a "FreeBuilding" method or an lua direct-addition method will not cause the handler code to execute at that time for that building in that city, as these types of building-additions to a city do not cause the CityConstructed game event to fire. The next Player Turn Processing will cause these buildings to be properly handled.
  8. Each sub-table for a primary "key,value" pair accepts the following data (red are required, green are optional):
    1. ApplyToAllInClass:
      When set to "true", as in
      Code:
      ApplyToAllInClass=true
      All buildings from within the same Building-Class will be given the same plot-improvement handling. Note as example I have set this very requirement for the BUILDING_WORKSHOP.

      Important Note: only use this setting when the structure of the Building-Class is such that there is a Default Building within the Building-Class, and every other building within the Building-Class is assigned to a specific civilization as a Unique Replacement Building.​
    2. SpecialHandling:
      This is used to define a special function when the desire is not to add dummy buildings, but to take some other action when the city has the primary building. This can be omitted or set as
      Code:
      SpecialHandling="NONE"
      when no special kind of effect is required other than adding dummy buildings to the city.​
    3. Counters={ }:
      You must define this within the "value" sub-table of each primary "key,value" pair. This is where you enter the information for the counts you want the handler to make. You must define at least one count within this table "Counters". If we look at the code for the Granary building in the sample, we have:
      Code:
      Counters={
      {DummyBuilding=GameInfoTypes["BUILDING_GRANARY_WHEAT_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_FARM, ResourceType=GameInfoTypes.RESOURCE_WHEAT, LimitPerCity=4, PlotsPerEffect=1},
      {DummyBuilding=GameInfoTypes["BUILDING_GRANARY_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_CAMP, ResourceType=GameInfoTypes.RESOURCE_DEER, MustBeWorked=false, LimitPerCity=2, PlotsPerEffect=1},
      {DummyBuilding=GameInfoTypes["BUILDING_GRANARY_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_FARM, MustBeWorked=false, DoNotCountResourceTiles=true, LimitPerCity=2, PlotsPerEffect=1}
      }
      1. Each Count-Method we wish to use is a subtable within the larger "Counters" table for the Granary Building
      2. We don't specify "keys" within the larger "Counters" table, just the "value" (ie, the sub-table within the larger "Counters" table for the Granary Building)
      3. Each Count Method can have its own improvement, or multiple Count Methods can refer to the same improvement as is done with Farm, where one Count Method is counting Farms-on-Wheat, and other Farms-not-on-Wheat.
      4. A desired dummy building can be used in multiple Count Methods, as is being done with the BUILDING_GRANARY_DUMMY, where the Count Methods for Camps-With-Deer and Farms-not-on-Wheat both add to the number of BUILDING_GRANARY_DUMMY added to a city.
    4. Discussion on the parameters each Count Method requires or can accept are covered in the next post
    5. Discussion on impementing a SpecialHandling function will be covered in the next post
 
Parameters Available for Each "Count Method":

Each counter within a "Counters" table for a primary building can take varying parameters (again, red are required within each 'counter', and green are optional, and have default values when omitted):
  • DummyBuilding: You must specify one of the following two types of information
    1. A valid Building ID# for a Building from within table <Buildings>
    2. A text string to be used to track the counts, as I used in the sample code for two of the Count Methods used in the Workshop Building. When using text strings it is better to have every such text string you are using be unique and different from every other you are using.
      • Important: Whe you use this text-string method you must also create and add a SpecialHandling function for the code to run, as I did in the example for the Workshop. If you fail to add a SpecialHandling function, you will get an error message in the lua log as well as a In-Game Notification of Invalid Data within table tRealBuildingData. The code will also disable itself for this type of an error.
  • ImprovementType: Sepcifies the Desired Terrain Improvement for counting.
    1. This must be a valid Improvement ID# from within the game's <Improvements> table. Without it, the code cannot know what to count.
  • ResourceType: Two methods are allowed to specify a resource that can/must be present on the improvement tile.
    1. A valid Resource ID# from within the game's <Resources> table. When specified, only an improvement with that specific resource will be counted. You can set up multiple Count Methods each referring to the same DummyBuilding and ImprovementType, but with different designations of ResourceType, and the sum total of these will be the number of copies of the DummyBuilding that will be added to the city.
    2. Omission. In this case unless DoNotCountResourceTiles is also set as true, any and all of the specified improvement will be counted, regardless of what if any Resources are also on the tile.
  • LimitPerCity: Designates a limitation on the number of tiles that can affect a single city.
    1. This can be omitted, in which case it defaults to "-1", which the handler code interprets as being infinite number of improvements can be accepted for a single city. Obviously, though, a city could only have 36 of the same improvement without a DLL-mod to increase a city's working radius.
    2. Do not set this number to '0', since the code is written to look for numbers larger than '0' when accounting for a LimitPerCity. If you really need to enter '0' for this, what is the point of making the code perform this count?
  • PlotsPerEffect: This allows you to specify that for example one DummyBuilding will be added for every 2 qualifying improvements found
    1. It can be omitted. When omitted, no 'division' is made on the original count of the improvements.
    2. This acts essentially as a 'divisor', so when entering a value for this parameter, bear in mind that the result of the calculations will be
      math.floor(OriginalCountValue/PlotsPerEffect)
    3. If you specify '2' for PlotsPerEffect, and there is only one (1) qualifying tile near a city, you will get '0'.
  • MustBeWorked: Tells the code whether the Plot must be worked by the city in order to be counted. Must be omitted or set as either of true or false.
    1. Defaults to true when omitted.
    2. Note: when any city is working a given plot only that city will ever be able to "count" that plot. MustBeWorked only has effect on plots which are not being worked.
  • DoNotCountResourceTiles: Tells the code whether Resource Tiles should be counted. Must be omitted or set as either of true or false
    1. Defaults to false when omitted.
    2. Should not be set to true when a specification is made for ResourceType as such a combination of settings would be directly clashing with each other.
  • MustBeOwned: Tells the code whether the Plot must be owned by the Player in order to be counted. Must be omitted or set as either of true or false
    1. Defaults to true when omitted.
  • CannotBePillaged: Tells the code whether the Plot can be Pillaged in order to be counted. Must be omitted or set as either of true or false
    1. Defaults to true when omitted.
  • CountSameTileOnlyOnce: Tells the code whether the same Plot can be counted for more than one city. Must be omitted or set as either of true or false
    1. Defaults to true when omitted.
  • Note that when using PlotsPerEffect and LimitPerCity in the same Count Method, LimitPerCity will be enacted before PlotsPerEffect is considered. To if I want a Count Method to only add 2 copies of a Dummy Building, and I want each Dummy Building added to require 2 Improvements, then I need to state that PlotsPerEffect=2 and LimitPerCity=4.



Creating a SpecialHandling Function when Needed:
  1. If you don't want to implement dummy buildings as a direct result of all the counts that are made by the handler, then you can create a customized function and tell the handler to run that function after all the counts are complete and a city has the primary building
  2. Reviewing this part of the code that was used as an example in the previous post
    Code:
    tRealBuildingData[color="red"][GameInfoTypes.BUILDING_WORKSHOP][/color] = {ApplyToAllInClass=true,
    [color="green"]Counters={ {DummyBuilding=GameInfoTypes["BUILDING_WORKSHOP_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, LimitPerCity=-1 },
    {DummyBuilding="LUMBERMILL_TOTAL", ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, MustBeOwned=false, MustBeWorked=false, LimitPerCity=-1 },
    {DummyBuilding="LUMBERMILL_OWNED", ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, MustBeOwned=true, MustBeWorked=false, LimitPerCity=-1 }
    }[/color] }
    tRealBuildingData[color="red"][GameInfoTypes.BUILDING_WORKSHOP][/color][color="blue"].SpecialHandling = (
    	function(BuildingID, tDummyQuantitiesTable, pCity, iPlayer)
    		local pPlayer = Players[iPlayer]
    		if not pPlayer:IsHuman() then return end
    		local iDummyBuilding = GameInfoTypes["BUILDING_WORKSHOP_DUMMY"]
    		local iOwnedLumbermills = tDummyQuantitiesTable["LUMBERMILL_OWNED"]
    		local iTotalLumbermills = tDummyQuantitiesTable["LUMBERMILL_TOTAL"]
    		local iNumberDummyToAdd = tDummyQuantitiesTable[iDummyBuilding]
    		local sDummyBuildingName = Locale.ConvertTextKey(GameInfo.Buildings[iDummyBuilding].Description)
    		print("The total number of Lumbermills within the working range of " .. pCity:GetName() .. " is " .. iTotalLumbermills)
    		print("The total number of Lumbermills within the working range of " .. pCity:GetName() .. " that are owned by player " .. pPlayer:GetName() .. " is " .. iOwnedLumbermills)
    		print("The total number of Dummy Buildings " .. sDummyBuildingName .. " that should be added if " .. pCity:GetName() .. " has a Workshop-Class Building is " .. iNumberDummyToAdd)
    		if pCity:IsHasBuilding(BuildingID) then
    			pCity:SetNumRealBuilding(iDummyBuilding, iNumberDummyToAdd)
    		else pCity:SetNumRealBuilding(iDummyBuilding, 0)
    		end
    	end
    	)[/color]
    • Red highlighting is a primary "key" in table tRealBuildingData, as in [GameInfoTypes.BUILDING_WORKSHOP]
    • Green highlighting is the counters we want to execute when a city has the primary building [GameInfoTypes.BUILDING_WORKSHOP]
    • Blue highlighting is the hookup of the special function we want to run after the counts are all made. You should refer to the "BuildingID" within your function, as is done in the example function, and not the Red [GameInfoTypes.BUILDING_WORKSHOP].
  3. Four Parameters are passed to any special function hooked into the handler in this manner, and they must be stated in the line
    Code:
    function(BuildingID, tDummyQuantitiesTable, pCity, iPlayer)
    exactly as is shown in terms of their order in the list of parameters.
    • BuildingID will be the same as the primary building ID#. In other words, in this example, it will be the same as [GameInfoTypes.BUILDING_WORKSHOP]
      • Since in the example code, "ApplyToAllInClass" is set to true, an Iroquois player will get the ID# of the Longhouse, which is the reasoning for never referring within the "Blue" code to "GameInfoTypes.BUILDING_WORKSHOP" but rather to the passed "BuildingID"
    • tDummyQuantitiesTable will be the count results for the primary building [GameInfoTypes.BUILDING_WORKSHOP]
      1. It wil be an lua table with "key,value" pairs in a form like this (for the example we are using):
        Key Data Value Data
        ID# of BUILDING_WORKSHOP_DUMMY # of Lumbermills that are being worked by the city
        "LUMBERMILL_TOTAL" total # of Lumbermills found within the working range of the city
        "LUMBERMILL_OWNED" total # of Lumbermills within the working range of the city that are owned by the player
      2. If I change the "Counters" data for [GameInfoTypes.BUILDING_WORKSHOP] to this:
        Code:
        [color="green"]Counters={ {DummyBuilding=GameInfoTypes["BUILDING_WORKSHOP_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, LimitPerCity=-1 },
        {DummyBuilding=GameInfoTypes["BUILDING_WORKSHOP_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_QUARRY, LimitPerCity=-1 },
        {DummyBuilding="LUMBERMILL_TOTAL", ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, MustBeOwned=false, MustBeWorked=false, LimitPerCity=-1 },
        {DummyBuilding="LUMBERMILL_OWNED", ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, MustBeOwned=true, MustBeWorked=false, LimitPerCity=-1 }
        }[/color]
        then the data given in the table tDummyQuantitiesTable will be as
        Key Data Value Data
        ID# of BUILDING_WORKSHOP_DUMMY # of Lumbermills and Quarries that are being worked by the city
        "LUMBERMILL_TOTAL" total # of Lumbermills found within the working range of the city
        "LUMBERMILL_OWNED" total # of Lumbermills within the working range of the city that are owned by the player
      3. If the current city being processed does not have the primary building (ie, [GameInfoTypes.BUILDING_WORKSHOP]) then all the value data in table tDummyQuantitiesTable will be '0'
        • This is because counts are not performed around a city for a primary building a city does not have. The starting values of '0' that are assigned before counts are made for a specific city will still apply for any primary building that specific city does not have
    • pCity will be the city reference for use in methods such as pCity:GetName()
    • iPlayer will be the player ID#
  4. Note the the only reason I added this line to the sample function
    Code:
    if not pPlayer:IsHuman() then return end
    was that I got a huge clog of messages printed into the lua.log file. For testing purposes this was not very user-friendly. As a general thing for code you actually want to execute as part of your mod, you should not add print statements to a special handling function unless you are attempting to debug what is happening within the special function.
  5. Just like with any other lua code, once you have the data for how many of the desired improvements are around the city, you can cause the special function to take any desired action to get the effect you are after.

Further Example of Configuring Table tRealBuildingData:
  1. Lets assume we want Shrines (and all unique versions of Shrines) to add +1 'Global' Happiness for every worked Holy Site being worked by the same city.
    • We can't add any form of "Happiness" directly to any Terrain-Improvement. There is no game table in the Improvements-XML schema to allow this, nor of course is there a table within the Buildings-XML that would allow it either.
    • An lua method of some kind must be employed since we cannot do it through XML-only methods.
    • If we use the handler, and implement dummy buildings in any city that has a Shrine-Class building and which is also working Holy Sites, we can create this effect.
  2. First, a Dummy Building is probably the easiest method to achieve this effect rather than direct-lua commands in a 'SpecialHandling' function registered with the Handler.
    • So we create a dummy building that has this as its xml-definition:
      Spoiler :
      1. As defined this building would be hidden from view in BNW
      2. Because of the use of column "GreatWorkCount" the building would also fail to be added to the game's database in Vanilla and G&K
      3. So, be aware of these issues when structuring a Dummy Building to be used with the Handler
      Code:
      <GameData>
      	<Buildings>
      		<!-- Shrines Holy Site Dummy -->
      		<Row>
      			<Type>BUILDING_SHRINE_HS_HAPPINESS_DUMMY</Type>
      			<BuildingClass>BUILDINGCLASS_SHRINE_HS_HAPPINESS_DUMMY</BuildingClass>
      			<Cost>-1</Cost>
      			<FaithCost>-1</FaithCost>
      			<PrereqTech>NULL</PrereqTech>
      			<GreatWorkCount>-1</GreatWorkCount>
      			<ArtDefineTag>NONE</ArtDefineTag>
      			<MinAreaSize>-1</MinAreaSize>
      			<NeverCapture>true</NeverCapture>
      			<HurryCostModifier>-1</HurryCostModifier>
      			<IconAtlas>BW_ATLAS_1</IconAtlas>
      			<PortraitIndex>19</PortraitIndex>
      			<Description>TXT_KEY_SHRINE_HS_HAPPINESS_DUMMY</Description>
      			<UnmoddedHappiness>1</UnmoddedHappiness>
      		</Row>
      	</Buildings>
      	<BuildingClasses>
      		<Row>
      			<Type>BUILDINGCLASS_SHRINE_HS_HAPPINESS_DUMMY</Type>
      			<DefaultBuilding>BUILDING_SHRINE_HS_HAPPINESS_DUMMY</DefaultBuilding>
      			<Description>TXT_KEY_SHRINE_HS_HAPPINESS_DUMMY</Description>
      			<NoLimit>true</NoLimit>
      		</Row>
      	</BuildingClasses>
      	<Language_en_US>
      		<Row Tag="TXT_KEY_SHRINE_HS_HAPPINESS_DUMMY">
      			<Text>Shrines HolySite Happy</Text>
      		</Row>
      	</Language_en_US>
      </GameData>
  3. We need to configure file CityBuildingsToNearbyImprovementsHandler.lua so it uses our desired information.
  4. The easiest method is to first open file CityBuildingsToNearbyImprovementsHandler.lua and delete the lines highlighted in red entirely
    Spoiler :
    Code:
    ----------------------------------------------------------------
    --enter data into table tRealBuildingData here
    
    --each subtable with table 'Counters' for a 'Real' building MUST have a designation of 'DummyBuilding=XXXXX'
    ----------------------------------------------------------------
    
    
    [color="red"]tRealBuildingData[GameInfoTypes.BUILDING_GRANARY] = {
    Counters={
    {DummyBuilding=GameInfoTypes["BUILDING_GRANARY_WHEAT_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_FARM, ResourceType=GameInfoTypes.RESOURCE_WHEAT, LimitPerCity=4, PlotsPerEffect=1},
    {DummyBuilding=GameInfoTypes["BUILDING_GRANARY_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_CAMP, ResourceType=GameInfoTypes.RESOURCE_DEER, MustBeWorked=false, LimitPerCity=2, PlotsPerEffect=1},
    {DummyBuilding=GameInfoTypes["BUILDING_GRANARY_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_FARM, MustBeWorked=false, DoNotCountResourceTiles=true, LimitPerCity=2, PlotsPerEffect=1}
    } }
    tRealBuildingData[GameInfoTypes.BUILDING_WORKSHOP] = {ApplyToAllInClass=true,
    Counters={ {DummyBuilding=GameInfoTypes["BUILDING_WORKSHOP_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, LimitPerCity=-1 },
    {DummyBuilding="LUMBERMILL_TOTAL", ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, MustBeOwned=false, MustBeWorked=false, LimitPerCity=-1 },
    {DummyBuilding="LUMBERMILL_OWNED", ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, MustBeOwned=true, MustBeWorked=false, LimitPerCity=-1 }
    } }
    tRealBuildingData[GameInfoTypes.BUILDING_WORKSHOP].SpecialHandling = (
    	function(BuildingID, tDummyQuantitiesTable, pCity, iPlayer)
    		local pPlayer = Players[iPlayer]
    		if not pPlayer:IsHuman() then return end
    		local iDummyBuilding = GameInfoTypes["BUILDING_WORKSHOP_DUMMY"]
    		local iOwnedLumbermills = tDummyQuantitiesTable["LUMBERMILL_OWNED"]
    		local iTotalLumbermills = tDummyQuantitiesTable["LUMBERMILL_TOTAL"]
    		local iNumberDummyToAdd = tDummyQuantitiesTable[iDummyBuilding]
    		local sDummyBuildingName = Locale.ConvertTextKey(GameInfo.Buildings[iDummyBuilding].Description)
    		print("The total number of Lumbermills within the working range of " .. pCity:GetName() .. " is " .. iTotalLumbermills)
    		print("The total number of Lumbermills within the working range of " .. pCity:GetName() .. " that are owned by player " .. pPlayer:GetName() .. " is " .. iOwnedLumbermills)
    		print("The total number of Dummy Buildings " .. sDummyBuildingName .. " that should be added if " .. pCity:GetName() .. " has a Workshop-Class Building is " .. iNumberDummyToAdd)
    		if pCity:IsHasBuilding(BuildingID) then
    			pCity:SetNumRealBuilding(iDummyBuilding, iNumberDummyToAdd)
    		else pCity:SetNumRealBuilding(iDummyBuilding, 0)
    		end
    	end
    	)[/color]
    So that we are working to a clean slate.
  5. Then, type in the following
    Code:
    tRealBuildingData[GameInfoTypes.BUILDING_SHRINE] = { }
    so that in place of the original chunk of code we now have
    Code:
    ----------------------------------------------------------------
    --enter data into table tRealBuildingData here
    
    --each subtable with table 'Counters' for a 'Real' building MUST have a designation of 'DummyBuilding=XXXXX'
    ----------------------------------------------------------------
    
    tRealBuildingData[GameInfoTypes.BUILDING_SHRINE] = { }
    Note that I am not going to include the lines of comments anymore, I am only going to show the actual contruction of table tRealBuildingData
  6. We want our code to include all unique versions of a Shrine, so we need to add "ApplyToAllInClass=true", and we may as well create the "Counters" sub-table at the same time:
    Code:
    tRealBuildingData[GameInfoTypes.BUILDING_SHRINE] = {ApplyToAllInClass=true, Counters={ } }
    We won't need a designation for "SpecialHandling", so that can be omitted
  7. Now we tell the code what to count when a city has a Shrine-Class Building.
    • We need to specify the DummyBuilding we want to implement (BUILDING_SHRINE_HS_HAPPINESS_DUMMY)
    • We need to specify the Improvement we want to count (IMPROVEMENT_HOLY_SITE)
    • We don't care if the Holy Site has been constructed on a Resource. All we care about is whether there is a Holy Site.
    • We want to require that the plot be worked by the city. Since this is the default setting unless we otherwise specify, we don't need to include anything for this requirement.
    • "MustBeOwned=true" is also the default setting, as is "LimitPerCity=-1".
    • In fact, because of the structuring of the default settings for "Counts" within a "Counters" subtable, we only need the designation for the "DummyBuilding" and the "ImprovementType".
    Our code to configure table tRealBuildingData is:
    Code:
    tRealBuildingData[GameInfoTypes.BUILDING_SHRINE] = {ApplyToAllInClass=true, Counters={ {DummyBuilding=GameInfoTypes.BUILDING_SHRINE_HS_HAPPINESS_DUMMY, ImprovementType=GameInfoTypes.IMPROVEMENT_HOLY_SITE } } }
  8. We do not actually need anything else.
 
Last edited:
In-Game Error Messages:
  1. There are several error-types that will cause the code to generate an in-game error message as a generic notification.
  2. When such error messages appear, the code of the handler will be disabled. This is because the errors are only generated when the data entered has created a non-reconcilable conflict.
  3. The notification will occur as soon as you press either of "Begin Your Journey" or "Continue Your Journey". They will not re-occur for the same game-session.
  4. The text of the error messages is also printed to the game's lua log.
  5. Basic View of an error notification as it 'slides' down the UI:
  6. Hovering over an error message will give as full details as is possible:
    • A resource was specified for counting, but 'DoNotCountResourceTiles' was also specified:
    • An invalid Building ID# was passed as the dummy building's ID#:
 
In case anyone was keeping an eye on this thread for an announcement that I had found and eliminated the annoying bug mentioned earlier, this has been cured and the download link is now here.
 
Hey LeeS,

I followed your instructions here and pasted it in my mod. However there seems to be an error here from FireTuner and it's not really working for some reason:

Code:
[32653.359] Runtime Error: C:\Users\Admin\Documents\My Games\Sid Meier's Civilization 5\MODS\Donkey Kong Donkey Kong and DK Island (BNW Only) (v 1)\Lua/BuildingsToNearbyImprovements.lua:94: attempt to index local 'CounterData' (a number value)
stack traceback:
	C:\Users\Admin\Documents\My Games\Sid Meier's Civilization 5\MODS\Donkey Kong Donkey Kong and DK Island (BNW Only) (v 1)\Lua/BuildingsToNearbyImprovements.lua:94: in main chunk
	=[C]: ?
[32653.359] Runtime Error: Error loading C:\Users\Admin\Documents\My Games\Sid Meier's Civilization 5\MODS\Donkey Kong Donkey Kong and DK Island (BNW Only) (v 1)\Lua/BuildingsToNearbyImprovements.lua.

I've also got the file itself, too.
 

Attachments

  • BuildingsToNearbyImprovements.txt
    21.4 KB · Views: 298
Hey LeeS,

I followed your instructions here and pasted it in my mod. However there seems to be an error here from FireTuner and it's not really working for some reason:

Code:
[32653.359] Runtime Error: C:\Users\Admin\Documents\My Games\Sid Meier's Civilization 5\MODS\Donkey Kong Donkey Kong and DK Island (BNW Only) (v 1)\Lua/BuildingsToNearbyImprovements.lua:94: attempt to index local 'CounterData' (a number value)
stack traceback:
	C:\Users\Admin\Documents\My Games\Sid Meier's Civilization 5\MODS\Donkey Kong Donkey Kong and DK Island (BNW Only) (v 1)\Lua/BuildingsToNearbyImprovements.lua:94: in main chunk
	=[C]: ?
[32653.359] Runtime Error: Error loading C:\Users\Admin\Documents\My Games\Sid Meier's Civilization 5\MODS\Donkey Kong Donkey Kong and DK Island (BNW Only) (v 1)\Lua/BuildingsToNearbyImprovements.lua.

I've also got the file itself, too.
My goof, it needs to be
Code:
tRealBuildingData[GameInfoTypes.BUILDING_TREEHOUSE] = { Counters= { {DummyBuilding=GameInfoTypes["BUILDING_TREEHOUSE_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_PLANTATION, LimitPerCity=-1} } }
I missed a pair of "{}" in the other thread, which I will also go fix over there for posterity's sake.
 
My goof, it needs to be
Code:
tRealBuildingData[GameInfoTypes.BUILDING_TREEHOUSE] = { Counters= { {DummyBuilding=GameInfoTypes["BUILDING_TREEHOUSE_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_PLANTATION, LimitPerCity=-1} } }
I missed a pair of "{}" in the other thread, which I will also go fix over there for posterity's sake.

Well, we all make mistakes.

Anyways, thanks for the help again.
 
How do I tweak this script so that my Fairgrounds building boosts improvement yields nearby...say +1 Food per Farm, +1 Gold per Plantation, and +1 Production per Pasture closest to City X?
 
How do I tweak this script so that my Fairgrounds building boosts improvement yields nearby...say +1 Food per Farm, +1 Gold per Plantation, and +1 Production per Pasture closest to City X?

You would change this part of the code at the top of the file:
Spoiler :
Code:
-- CityBuildingsToNearbyImprovementsHandler
-- Author: LeeS
-- DateCreated: 4/22/2016 3:49:21 PM
--------------------------------------------------------------
local tRealBuildingData = {}	--don't change this line
--------------------------------------------------------------
--[[

USER CONFIGURABLE VARIABLES

	Make changes to these variables as desired and needed

]]--
--------------------------------------------------------------

local bRequiresSpecificCiv = false
local iSpecificRequiredCiv = GameInfoTypes["CIVILIZATION_AMERICA"]
local bDoNotApplyToMinorCivs = true




----------------------------------------------------------------
--enter data into table tRealBuildingData here

--each subtable with table 'Counters' for a 'Real' building MUST have a designation of 'DummyBuilding=XXXXX'
----------------------------------------------------------------


tRealBuildingData[GameInfoTypes.BUILDING_GRANARY] = { Counters={
{DummyBuilding=GameInfoTypes["BUILDING_GRANARY_WHEAT_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_FARM, ResourceType=GameInfoTypes.RESOURCE_WHEAT, LimitPerCity=4, PlotsPerEffect=1},
{DummyBuilding=GameInfoTypes["BUILDING_GRANARY_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_CAMP, ResourceType=GameInfoTypes.RESOURCE_DEER, MustBeWorked=false, LimitPerCity=2, PlotsPerEffect=1},
{DummyBuilding=GameInfoTypes["BUILDING_GRANARY_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_FARM, MustBeWorked=false, DoNotCountResourceTiles=true, LimitPerCity=2, PlotsPerEffect=1}
} }
tRealBuildingData[GameInfoTypes.BUILDING_WORKSHOP] = {ApplyToAllInClass=true, Counters={ {DummyBuilding=GameInfoTypes["BUILDING_WORKSHOP_DUMMY"], ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, LimitPerCity=-1 },
{DummyBuilding="LUMBERMILL_TOTAL", ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, MustBeOwned=false, MustBeWorked=false, LimitPerCity=-1 },
{DummyBuilding="LUMBERMILL_OWNED", ImprovementType=GameInfoTypes.IMPROVEMENT_LUMBERMILL, MustBeOwned=true, MustBeWorked=false, LimitPerCity=-1 }}}
tRealBuildingData[GameInfoTypes.BUILDING_WORKSHOP].SpecialHandling = (
	function(BuildingID, tDummyQuantitiesTable, pCity, iPlayer)
		local pPlayer = Players[iPlayer]
		if not pPlayer:IsHuman() then return end
		local iDummyBuilding = GameInfoTypes["BUILDING_WORKSHOP_DUMMY"]
		local iOwnedLumbermills = tDummyQuantitiesTable["LUMBERMILL_OWNED"]
		local iTotalLumbermills = tDummyQuantitiesTable["LUMBERMILL_TOTAL"]
		local iNumberDummyToAdd = tDummyQuantitiesTable[iDummyBuilding]
		local sDummyBuildingName = Locale.ConvertTextKey(GameInfo.Buildings[iDummyBuilding].Description)
		print("The total number of Lumbermills within the working range of " .. pCity:GetName() .. " is " .. iTotalLumbermills)
		print("The total number of Lumbermills within the working range of " .. pCity:GetName() .. " that are owned by player " .. pPlayer:GetName() .. " is " .. iOwnedLumbermills)
		print("The total number of Dummy Buildings " .. sDummyBuildingName .. " that should be added if " .. pCity:GetName() .. " has a Workshop-Class Building is " .. iNumberDummyToAdd)
		if pCity:IsHasBuilding(BuildingID) then
			pCity:SetNumRealBuilding(iDummyBuilding, iNumberDummyToAdd)
		else pCity:SetNumRealBuilding(iDummyBuilding, 0)
		end
	end
	)













------xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
--Don't make changes in the following code
------xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
To read as follows:
Spoiler lua :
Code:
-- CityBuildingsToNearbyImprovementsHandler
-- Author: LeeS
-- DateCreated: 4/22/2016 3:49:21 PM
--------------------------------------------------------------
local tRealBuildingData = {}	--don't change this line
--------------------------------------------------------------
--[[

USER CONFIGURABLE VARIABLES

	Make changes to these variables as desired and needed

]]--
--------------------------------------------------------------

local bRequiresSpecificCiv = false
       --this tells the code to apply the effect for all civs
local iSpecificRequiredCiv = GameInfoTypes["CIVILIZATION_AMERICA"]
       --we still need this as a code placeholder but it will not do anything
local bDoNotApplyToMinorCivs = true
       --this tells the code not to apply the effect to City-States

tRealBuildingData[GameInfoTypes.BUILDING_ISN_FAIRGROUNDS] = { Counters={
{DummyBuilding=GameInfoTypes["BUILDING_ISN_FAIRGROUNDS_FARM"], ImprovementType=GameInfoTypes.IMPROVEMENT_FARM },
{DummyBuilding=GameInfoTypes["BUILDING_ISN_FAIRGROUNDS_PLANTATION"], ImprovementType=GameInfoTypes.IMPROVEMENT_PLANTATION },
{DummyBuilding=GameInfoTypes["BUILDING_ISN_FAIRGROUNDS_PASTURE"], ImprovementType=GameInfoTypes.IMPROVEMENT_PASTURE }
} }

------xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
--Don't make changes in the following code
------xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Then you make the designated dummy buildings create the +1 food, +1 gold, + 1 production effect when they are added to the city by the code:
Spoiler xml :
Code:
<GameData>
	<Buildings>
		<!-- Fairgrounds Farm Effect -->
		<Row>
			<Type>BUILDING_ISN_FAIRGROUNDS_FARM</Type>
			<BuildingClass>BUILDINGCLASS_ISN_FAIRGROUNDS_FARM</BuildingClass>
			<Cost>-1</Cost>
			<FaithCost>-1</FaithCost>
			<PrereqTech>NULL</PrereqTech>
			<GreatWorkCount>-1</GreatWorkCount>
			<ArtDefineTag>NONE</ArtDefineTag>
			<MinAreaSize>-1</MinAreaSize>
			<NeverCapture>true</NeverCapture>
			<HurryCostModifier>-1</HurryCostModifier>
			<IconAtlas>BW_ATLAS_1</IconAtlas>
			<PortraitIndex>19</PortraitIndex>
			<Description>TXT_KEY_BUILDING_ISN_FAIRGROUNDS_FARM</Description>
		</Row>
		<!-- Fairgrounds Plantation Effect -->
		<Row>
			<Type>BUILDING_ISN_FAIRGROUNDS_PLANTATION</Type>
			<BuildingClass>BUILDINGCLASS_ISN_FAIRGROUNDS_PLANTATION</BuildingClass>
			<Cost>-1</Cost>
			<FaithCost>-1</FaithCost>
			<PrereqTech>NULL</PrereqTech>
			<GreatWorkCount>-1</GreatWorkCount>
			<ArtDefineTag>NONE</ArtDefineTag>
			<MinAreaSize>-1</MinAreaSize>
			<NeverCapture>true</NeverCapture>
			<HurryCostModifier>-1</HurryCostModifier>
			<IconAtlas>BW_ATLAS_1</IconAtlas>
			<PortraitIndex>19</PortraitIndex>
			<Description>TXT_KEY_BUILDING_ISN_FAIRGROUNDS_PLANTATION</Description>
		</Row>
		<!-- Fairgrounds Pasture Effect -->
		<Row>
			<Type>BUILDING_ISN_FAIRGROUNDS_PASTURE</Type>
			<BuildingClass>BUILDINGCLASS_ISN_FAIRGROUNDS_PASTURE</BuildingClass>
			<Cost>-1</Cost>
			<FaithCost>-1</FaithCost>
			<PrereqTech>NULL</PrereqTech>
			<GreatWorkCount>-1</GreatWorkCount>
			<ArtDefineTag>NONE</ArtDefineTag>
			<MinAreaSize>-1</MinAreaSize>
			<NeverCapture>true</NeverCapture>
			<HurryCostModifier>-1</HurryCostModifier>
			<IconAtlas>BW_ATLAS_1</IconAtlas>
			<PortraitIndex>19</PortraitIndex>
			<Description>TXT_KEY_BUILDING_ISN_FAIRGROUNDS_PASTURE</Description>
		</Row>
	</Buildings>

	<BuildingClasses>
		<Row>
			<Type>BUILDINGCLASS_ISN_FAIRGROUNDS_FARM</Type>
			<DefaultBuilding>BUILDING_ISN_FAIRGROUNDS_FARM</DefaultBuilding>
			<Description>TXT_KEY_BUILDING_ISN_FAIRGROUNDS_FARM</Description>
		</Row>
		<Row>
			<Type>BUILDINGCLASS_ISN_FAIRGROUNDS_PLANTATION</Type>
			<DefaultBuilding>BUILDING_ISN_FAIRGROUNDS_PLANTATION</DefaultBuilding>
			<Description>TXT_KEY_BUILDING_ISN_FAIRGROUNDS_PLANTATION</Description>
		</Row>
		<Row>
			<Type>BUILDINGCLASS_ISN_FAIRGROUNDS_PASTURE</Type>
			<DefaultBuilding>BUILDING_ISN_FAIRGROUNDS_PASTURE</DefaultBuilding>
			<Description>TXT_KEY_BUILDING_ISN_FAIRGROUNDS_PASTURE</Description>
		</Row>
	</BuildingClasses>

	<Building_YieldChanges>
		<Row>
			<BuildingType>BUILDING_ISN_FAIRGROUNDS_FARM</BuildingType>
			<YieldType>YIELD_FOOD</YieldType>
			<Yield>1</Yield>
		</Row>
		<Row>
			<BuildingType>BUILDING_ISN_FAIRGROUNDS_PLANTATION</BuildingType>
			<YieldType>YIELD_GOLD</YieldType>
			<Yield>1</Yield>
		</Row>
		<Row>
			<BuildingType>BUILDING_ISN_FAIRGROUNDS_PASTURE</BuildingType>
			<YieldType>YIELD_PRODUCTION</YieldType>
			<Yield>1</Yield>
		</Row>
	</Building_YieldChanges>

	<Language_en_US>
		<Row Tag="TXT_KEY_BUILDING_ISN_FAIRGROUNDS_PLANTATION">
			<Text>Fairgrounds Plantation</Text>
		</Row>
		<Row Tag="TXT_KEY_BUILDING_ISN_FAIRGROUNDS_FARM">
			<Text>Fairgrounds Farm</Text>
		</Row>
		<Row Tag="TXT_KEY_BUILDING_ISN_FAIRGROUNDS_PASTURE">
			<Text>Fairgrounds Pasture</Text>
		</Row>
	</Language_en_US>

</GameData>
The effect will be shown as a direct building yield in the city view instead of a direct yield on the plot. It is possible to make the plot itself show the change in yield, but the problem with doing so is the code is far more complex and processing-heavy because there aree far more conditions you have to account for. Wherever possible, I advocate this sort of thing to be done directly using dummy buildings rather than far more complex direct-plot-yield manipulation via lua.

The city will have to be working the plots in order for the effect(s) to be added to the city. During player turn processing any changes to citizen assignments will be accounted for, as well as tile pillages etc. As set-up for the Fairgrounds and its dummies, the improvement cannot be pillaged either.

Important Note:
You will have to alter this designation in the lua as necessary to match with how you are actually defining the Fairgrounds building in your XML (the blue highlighted part):
Code:
tRealBuildingData[GameInfoTypes.[color="blue"]BUILDING_ISN_FAIRGROUNDS[/color]] = { Counters={ blah de blah........


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

ooops. I hit the submit button instead of the preview button.

Want to add that the dummy buildings will be hidden from view as coded, so if you want to be able to see them show up in the city-view for test purposes you will have to change in the xml
Code:
<GreatWorkCount>-1</GreatWorkCount>
to​
Code:
<!--	<GreatWorkCount>-1</GreatWorkCount>	-->
 
Firstly, thank you so much Lee for this resource. It's immensely useful! I have a problem, however.

I have implemented the Lua and it works (the intended effect occurs, it counts the improvements and gives the effect), however, I have "MustBeWorked" set to false, but the effects only seem to apply while the improved tiles are being worked. While I can live with this, I'd like for multiple cities to be able to benefit from the same tile, and this issue somewhat precludes this.

Here's my bit from the Lua, in case it's some obvious formatting error:
Code:
tRealBuildingData[GameInfoTypes.BUILDING_NAC_SHEPHERD_OF_THE_KASSITES] = {LimitPerCity=10, MustBeWorked=false, CountSameTileOnlyOnce=false, Counters={ {DummyBuilding=GameInfoTypes.BUILDING_NAC_KUDURRU_COUNTER, ImprovementType=GameInfoTypes.IMPROVEMENT_NAC_KUDURRU } } }

I'm not sure where I'm going wrong here.
 
It's literally been years since I wrote this code or looked at it, but a review of the relevant part of the handler shows that if a plot is being worked it will only be counted for the city that is actually working the plot regardless of any other settings in the file. The code only looks at the MustBeWorked parameter after it looks at whether the plot is being worked and whether the city working the plot is the same one as the code is currently making counts for.

The MustBeWorked parameter is intended to allow you to decide whether to count the tile when it is not being worked rather than act as an over-ride to the way the game usually treats plots which are being worked (ie, only one city can work a given plot and get its benefits).

I've amended the descriptions above to be more clear.
 
It's literally been years since I wrote this code or looked at it, but a review of the relevant part of the handler shows that if a plot is being worked it will only be counted for the city that is actually working the plot regardless of any other settings in the file. The code only looks at the MustBeWorked parameter after it looks at whether the plot is being worked and whether the city working the plot is the same one as the code is currently making counts for.

The MustBeWorked parameter is intended to allow you to decide whether to count the tile when it is not being worked rather than act as an over-ride to the way the game usually treats plots which are being worked (ie, only one city can work a given plot and get its benefits).

I've amended the descriptions above to be more clear.

Firstly, I appreciate the clarification! So if I'm understanding it correctly, the only way two cities could ever benefit from the same tile at the same time is if neither of the cities are working the tile? That's interesting!

Secondly, I realize I may have been unclear, but my issue is actually that even when it's just one city, the code is seemingly only picking up on the worked tiles, and the effect -- in my case, a % bonus to building production -- is only appearing when the tiles are being worked (after I pass the turn, of course). The effects were adding together correctly with the worked tiles, but it didn't seem like the non-worked tiles were being counted at all. I was wondering if maybe I had formatted something wrong in the counter and had it disregard the MustBeWorked parameter or something.

Edit: After further testing, it also does not appear to be respecting the "LimitPerCity=10" bit, which inclines me to believe that it is just going to defaults and I've mucked up the formatting somehow..
 
Last edited:
I have an idea what might be going on but not sure how long it will take to update the code and post a "fix" file if what I think may be happening is actually happening.
 
No rush! I will fiddle with it a bit and see if I can get any of it to work. If you don't mind giving a brief explanation, what do you suspect is going on?
 
Part of it I think is if there are resources on the plot where the improvement is located, this will result in the Plot:GetResourceType() method retrieving a positive value, but when the "ResourceType" parameter is left empty the code of the handler uses a "-1" default value in constructing the tables needed to determine the counting methods. So only plots with no resources, hidden or revealed, will be counted. When no TeamID argument for the Plot:GetResourceType() method is supplied you get the resource that is on the tile regardless of whether the player has revealed it or not. So there's no "match" to the "iResourceType" in this line of the code
Code:
if tCityLocalImprovements[iImprovementType][BuildingID][iResourceType] then
so the main chunk of the counting method is not executed for such plots.

I was sure when I created this years ago it was all working properly but looking at it now years later I see there's an error in the logic -- I just have not as yet thought through the steps needed to account for all possible conditions I did not apparently account for previously in the code.
 
Wouldn't that mean that the counting should work for tiles without resources? (I doubt in my testing that they all had a hidden resource, I built about 12 of the tiles; all on seemingly resourceless tiles). Or are you saying that part/all of the settings bit is being thrown out because of an error, and that it's reverting to just counting the worked tiles since that's the default state?
 
No. The code is using a default setting of ResourceType == -1 when no ResourceType is set for the "Counts" table.
The code then matches for the passed ResourceType value from the Counts table as compared to the ResourceType the plot has. So if the plot has an actual resource on it of any kind, it will not be counted because there is no match to the "iResourceType" key in this line of the code
Code:
if tCityLocalImprovements[iImprovementType][BuildingID][iResourceType] then
This is the main conditional line of the counting methods and occurs before inspection of any other settings such as "MustBeWorked".

I also said that part of the problem was in this logic error, not that all of the problem was related to this logic error.

The fix needs to be along the lines of restructuring so that the counting logic is more in line with
Code:
if tCityLocalImprovements[iImprovementType][BuildingID][iResourceType] then
    --Plot Resource Matches the Required One in the Counts table
     --some new logic needs to be coded here
else
     --Plot has no resource or the resource does not match to the required one in the counts table
     --some other new logic needs to be coded here
end
It's the "new logic" and "other new logic" that needs to be determined so that the code actually functions in the way I thought I had coded it to function.

And the logic needs to be reworked so that when nothing is specified for the "ResourceType" parameter in the counters some value other than "-1" is used in the tables for "iResourceType" because omission of the "ResourceType" parameter was intended to act as any resource will be counted as well as no resource.

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

I think I can probably look more deeply into it over the weekend.
 
Last edited:
Ah, I appreciate the clarifications. The improvement I was testing can't be built on tiles with (known) resources, so I suppose I just may not have ran into that part. Thank you for doing all this!
 
Top Bottom