LeeS
Imperator
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.
- 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.
- 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.
- 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.
- 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.
- 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).
- 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
- a maximum number of a specific improvement that are allowed to affect any one city
- whether or not 1 copy of an Improvement = 1 Effect of some kind
- whether or not the tile must be worked
- 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)
- whether or not the player must own the tile
- whether or not a specified resource needs to be on the tile
- whether or not tiles with any resource should be ignored
- whether or not a pillaged tile should be counted
- In doing this it needs configurability for choices such as
- 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.
- Create a method that only scans through the plots surrounding a single city once per turn as part of PlayerDoTurn events processing.
- 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:
- The handler is downloadable from here: "City Building To Terrain Improvement Linkages" Download (CivFanatics Download Database link)
- 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.
- 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.
- The file CityBuildingsToNearbyImprovementsHandler.lua within the LUA folder is the only file you actually require from within the downloadable handler mod.
- 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
- 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
- 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:
- 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 )
- 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.
- This lineCode:
local tRealBuildingData = {} --don't change this line
- 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 ]]--
- We then have three variables we can configure:
Code:
local bRequiresSpecificCiv = false local iSpecificRequiredCiv = GameInfoTypes["CIVILIZATION_AMERICA"] local bDoNotApplyToMinorCivs = true
- 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
- 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.
- 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.
- 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.
- 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.
- If we take out all the data within the "value" subtable, and only look at the parts highlighted in blue above, we have
- Each sub-table for a primary "key,value" pair accepts the following data (red are required, green are optional):
- ApplyToAllInClass:
When set to "true", as inCode:ApplyToAllInClass=true
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. - 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 asCode:SpecialHandling="NONE"
- 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} }
- Each Count-Method we wish to use is a subtable within the larger "Counters" table for the Granary Building
- 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)
- 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.
- 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.
- Discussion on the parameters each Count Method requires or can accept are covered in the next post
- Discussion on impementing a SpecialHandling function will be covered in the next post
- ApplyToAllInClass: