Data driven code technique

whoward69

DLL Minion
Joined
May 30, 2011
Messages
8,699
Location
Near Portsmouth, UK
Consider the following code
Code:
  local random = Map.Rand(11, "Random National Wonder Bonuses Lua")
  if random == 0 then
    if city:CanConstruct(GameInfoTypes["[COLOR="Magenta"]BUILDING_NATIONAL_EPIC[/COLOR]"]) then
      local iProdValue = math.floor(city:GetBuildingProductionNeeded(GameInfoTypes["[COLOR="magenta"]BUILDING_NATIONAL_EPIC[/COLOR]"]) / 50)
      city:ChangeBuildingProduction(GameInfoTypes["[COLOR="magenta"]BUILDING_NATIONAL_EPIC[/COLOR]"], -iProdValue)
      if iProdValue > 0 then
        Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards [COLOR="Orange"]National Epic[/COLOR]!", iProdValue))
      end
    end
  end
  if random == 1 then
    if city:CanConstruct(GameInfoTypes["BUILDING_NATIONAL_COLLEGE"]) then
      local iProdValue = math.floor(city:GetBuildingProductionNeeded(GameInfoTypes["BUILDING_NATIONAL_COLLEGE"]) / 50)
      city:ChangeBuildingProduction(GameInfoTypes["BUILDING_NATIONAL_COLLEGE"], -iProdValue)
      if iProdValue > 0 then
        Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards National College!", iProdValue))
      end
    end
  end
  if random == 2 then
    if city:CanConstruct(GameInfoTypes["BUILDING_NATIONAL_TREASURY"]) then
      local iProdValue = math.floor(city:GetBuildingProductionNeeded(GameInfoTypes["BUILDING_NATIONAL_TREASURY"]) / 50)
      city:ChangeBuildingProduction(GameInfoTypes["BUILDING_NATIONAL_TREASURY"], -iProdValue)
      if iProdValue > 0 then
        Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards East India Company!", iProdValue))
      end
    end
  end
  if random == 3 then
    if city:CanConstruct(GameInfoTypes["BUILDING_HEROIC_EPIC"]) then
      local iProdValue = math.floor(city:GetBuildingProductionNeeded(GameInfoTypes["BUILDING_HEROIC_EPIC"]) / 50)
      city:ChangeBuildingProduction(GameInfoTypes["BUILDING_HEROIC_EPIC"], -iProdValue)
      if iProdValue > 0 then
        Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards Heroic Epic!", iProdValue))
      end
    end
  end
  if random == 4 then
    if city:CanConstruct(GameInfoTypes["BUILDING_CIRCUS_MAXIMUS"]) then
      local iProdValue = math.floor(city:GetBuildingProductionNeeded(GameInfoTypes["BUILDING_CIRCUS_MAXIMUS"]) / 50)
      city:ChangeBuildingProduction(GameInfoTypes["BUILDING_CIRCUS_MAXIMUS"], -iProdValue)
      if iProdValue > 0 then
        Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards Circus Maximus!", iProdValue))
      end
    end
  end
  if random == 5 then
    if city:CanConstruct(GameInfoTypes["BUILDING_GRAND_TEMPLE"]) then
      local iProdValue = math.floor(city:GetBuildingProductionNeeded(GameInfoTypes["BUILDING_GRAND_TEMPLE"]) / 50)
      city:ChangeBuildingProduction(GameInfoTypes["BUILDING_GRAND_TEMPLE"], -iProdValue)
      if iProdValue > 0 then
        Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards Grand Temple!", iProdValue))
      end
    end
  end
  if random == 6 then
    if city:CanConstruct(GameInfoTypes["BUILDING_IRONWORKS"]) then
      local iProdValue = math.floor(city:GetBuildingProductionNeeded(GameInfoTypes["BUILDING_IRONWORKS"]) / 50)
      city:ChangeBuildingProduction(GameInfoTypes["BUILDING_IRONWORKS"], -iProdValue)
      if iProdValue > 0 then
        Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards Ironworks!", iProdValue))
      end
    end
  end
  if random == 7 then
    if city:CanConstruct(GameInfoTypes["BUILDING_HERMITAGE"]) then
      local iProdValue = math.floor(city:GetBuildingProductionNeeded(GameInfoTypes["BUILDING_HERMITAGE"]) / 50)
      city:ChangeBuildingProduction(GameInfoTypes["BUILDING_HERMITAGE"], -iProdValue)
      if iProdValue > 0 then
        Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards Hermitage!", iProdValue))
      end
    end
  end
  if random == 8 then
    if city:CanConstruct(GameInfoTypes["BUILDING_OXFORD_UNIVERSITY"]) then
      local iProdValue = math.floor(city:GetBuildingProductionNeeded(GameInfoTypes["BUILDING_OXFORD_UNIVERSITY"]) / 50)
      city:ChangeBuildingProduction(GameInfoTypes["BUILDING_OXFORD_UNIVERSITY"], -iProdValue)
      if iProdValue > 0 then
        Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards Oxford University!", iProdValue))
      end
    end
  end
  if random == 9 then
    if city:CanConstruct(GameInfoTypes["BUILDING_TOURIST_CENTER"]) then
      local iProdValue = math.floor(city:GetBuildingProductionNeeded(GameInfoTypes["BUILDING_TOURIST_CENTER"]) / 50)
      city:ChangeBuildingProduction(GameInfoTypes["BUILDING_TOURIST_CENTER"], -iProdValue)
      if iProdValue > 0 then
        Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards National Visitor Center!", iProdValue))
      end
    end
  end
  if random == 10 then
    if city:CanConstruct(GameInfoTypes["BUILDING_INTELLIGENCE_AGENCY"]) then
      local iProdValue = math.floor(city:GetBuildingProductionNeeded(GameInfoTypes["BUILDING_INTELLIGENCE_AGENCY"]) / 50)
      city:ChangeBuildingProduction(GameInfoTypes["BUILDING_INTELLIGENCE_AGENCY"], -iProdValue)
      if iProdValue > 0 then
        Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards National Intelligence Agency!", iProdValue))
      end
    end
  end
nothing really wrong with it, it gets the job done, but apart from the building ID being tested and part of the message being output, it's the same code block repeated 11 times.
It could be written more compactly by using a function
Code:
function testAndAdd(city, plot, iBuilding, sTitle)
  if city:CanConstruct(iBuilding) then
    local iProdValue = math.floor(city:GetBuildingProductionNeeded(iBuilding) / 50)
    city:ChangeBuildingProduction(iBuilding, -iProdValue)
    if iProdValue > 0 then
      Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards {2_Title}!", iProdValue, sTitle))
    end
  end
end

  local random = Map.Rand(11, "Random National Wonder Bonuses Lua")
  if random == 0 then
    testAndAdd(city, plot, GameInfoTypes["BUILDING_NATIONAL_EPIC"], "National Epic")
  end
  if random == 1 then
    testAndAdd(city, plot, GameInfoTypes["BUILDING_NATIONAL_COLLEGE"], "National College")
  end
  if random == 2 then
    testAndAdd(city, plot, GameInfoTypes["BUILDING_NATIONAL_TREASURY"], "East India Company")
  end
  if random == 3 then
    testAndAdd(city, plot, GameInfoTypes["BUILDING_HEROIC_EPIC"], "Heroic Epic")
  end
  if random == 4 then
    testAndAdd(city, plot, GameInfoTypes["BUILDING_CIRCUS_MAXIMUS"], "Circus Maximus")
  end
  if random == 5 then
    testAndAdd(city, plot, GameInfoTypes["BUILDING_GRAND_TEMPLE"], "Grand Temple")
  end
  if random == 6 then
    testAndAdd(city, plot, GameInfoTypes["BUILDING_IRONWORKS"], "Ironworks")
  end
  if random == 7 then
    testAndAdd(city, plot, GameInfoTypes["BUILDING_HERMITAGE"], "Hermitage")
  end
  if random == 8 then
    testAndAdd(city, plot, GameInfoTypes["BUILDING_OXFORD_UNIVERSITY"], "Oxford University")
  end
  if random == 9 then
    testAndAdd(city, plot, GameInfoTypes["BUILDING_TOURIST_CENTER"], "National Visitor Center")
  end
  if random == 10 then
    testAndAdd(city, plot, GameInfoTypes["BUILDING_INTELLIGENCE_AGENCY"], "Intelligence Agency")
  end
but we still end up with 11 repeated code blocks (and we're also passing city and plot every time which kinda defeats and bonus from having them local outside of the big if block)
It can be written much more compactly by using a technique known as "data-driven code".
Rather then writing a code block for each variation, we store each variation (in this case the id and message part) into a table, and then write one generic code block that can be varied from one "line" of data in the table

Our table (which would be placed outside of all functions with the Lua file) contains the 11 variations
Code:
local nationalWonders = {
  {id=GameInfoTypes.BUILDING_NATIONAL_EPIC, title="National Epic"},
  {id=GameInfoTypes.BUILDING_NATIONAL_COLLEGE, title="National College"},
  {id=GameInfoTypes.BUILDING_NATIONAL_TREASURY, title="East India Company"},
  {id=GameInfoTypes.BUILDING_HEROIC_EPIC, title="Heroic Epic"},
  {id=GameInfoTypes.BUILDING_CIRCUS_MAXIMUS, title="Circus Maximus"},
  {id=GameInfoTypes.BUILDING_GRAND_TEMPLE, title="Grand Temple"},
  {id=GameInfoTypes.BUILDING_IRONWORKS, title="Ironworks"},
  {id=GameInfoTypes.BUILDING_HERMITAGE, title="Hermitage"},
  {id=GameInfoTypes.BUILDING_OXFORD_UNIVERSITY, title="Oxford University"},
  {id=GameInfoTypes.BUILDING_TOURIST_CENTER, title="National Visitor Center"},
  {id=GameInfoTypes.BUILDING_INTELLIGENCE_AGENCY, title="National Intelligence Agency"}
}
and our code then picks a "line" at random from the table and feeds it into a generic code block
Code:
-- Note the use of #nationalWonders here and not a hard coded 11
-- Also note the need to add 1 as Lua tables start from 1, not 0
local random = Map.Rand(#nationalWonders, "Random National Wonder Bonuses Lua") + 1

if city:CanConstruct(nationalWonders[random].id) then
  local iProdValue = math.floor(city:GetBuildingProductionNeeded(nationalWonders[random].id) / 50)
  city:ChangeBuildingProduction(nationalWonders[random].id, -iProdValue)
  if iProdValue > 0 then
    Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards {2_title}!", iProdValue, nationalWonders[random].title))
  end
end
If we now need to add additional "national wonders" (say the three guilds) all we have to do is add their corresponding rows to the nationalWonders table and the code will adapt with no further changes.

We can make a few additional tweaks to the code.
Given that we use nationalWonders[random].id three times, and it requires two table lookups (one to get the "line" and one to get the id from the line), it would be worth caching this, and at the same time we'll cache the message part
Code:
-- Note the use of #nationalWonders here and not a hard coded 11
local random = Map.Rand(#nationalWonders, "Random National Wonder Bonuses Lua")

local iRandomNationalWonder = nationalWonders[random].id
local iRandomNationalWonderTitle = nationalWonders[random].title
if city:CanConstruct(iRandomNationalWonder) then
  local iProdValue = math.floor(city:GetBuildingProductionNeeded(iRandomNationalWonder) / 50)
  city:ChangeBuildingProduction(iRandomNationalWonder, -iProdValue)
  if iProdValue > 0 then
    Events.AddPopupTextEvent(HexToWorld(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()))), Locale.ConvertTextKey("+{1_Num} [COLOR_POSITIVE_TEXT][ICON_PRODUCTION]Production[ENDCOLOR] towards {2_title}!", iProdValue, iRandomNationalWonderTitle))
  end
end

In fact we don't need to store the message part, as that's just the building's description from the database, so we can replace
Code:
local iRandomNationalWonderTitle = nationalWonders[random].title
with
Code:
local iRandomNationalWonderTitle = GameInfo.Buildings[iRandomNationalWonder].Description
(note that the Locale.ConvertTextKey() function will take care of converting the TXT_KEY in the Description into the required language text)

Which means we don't need to store the title in the table, so we can reduce our nationalWonders table to just
Code:
local nationalWonders = {
  GameInfoTypes.BUILDING_NATIONAL_EPIC,
  GameInfoTypes.BUILDING_NATIONAL_COLLEGE,
  GameInfoTypes.BUILDING_NATIONAL_TREASURY,
  GameInfoTypes.BUILDING_HEROIC_EPIC,
  GameInfoTypes.BUILDING_CIRCUS_MAXIMUS,
  GameInfoTypes.BUILDING_GRAND_TEMPLE,
  GameInfoTypes.BUILDING_IRONWORKS,
  GameInfoTypes.BUILDING_HERMITAGE,
  GameInfoTypes.BUILDING_OXFORD_UNIVERSITY,
  GameInfoTypes.BUILDING_TOURIST_CENTER,
  GameInfoTypes.BUILDING_INTELLIGENCE_AGENCY
}
with the associated change to
Code:
local iRandomNationalWonder = nationalWonders[random]

We can go further and remove the hard coding of id values in the National Wonders table.
A National Wonder is any building where the building class has a MaxPlayerInstances of 1 AND a corresponding entry in the Building_PrereqBuildingClasses table with a NumBuildingNeeded entry of -1.
The following SQL will select all of these buildings
Code:
SELECT b.ID, b.Type FROM Buildings b, BuildingClasses bc, Building_PrereqBuildingClasses pre
  WHERE bc.MaxPlayerInstances=1
  AND bc.Type=b.BuildingClass
  AND b.Type=pre.BuildingType
  AND pre.NumBuildingNeeded=-1;
so we can replace our hard-coded nationalWonders table with
Code:
local nationalWonders = {}
for row in DB.Query("SELECT b.ID FROM Buildings b, BuildingClasses bc, Building_PrereqBuildingClasses pre WHERE bc.MaxPlayerInstances=1 AND bc.Type=b.BuildingClass AND b.Type=pre.BuildingType AND pre.NumBuildingNeeded=-1") do
  table.insert(nationalWonders, row.ID)
end

Our final data-driven code is both much shorter (and hence easier to adapt), doesn't contain "almost duplicate" code (so is much easier to maintain/debug ... pretty sure that should just be iProdValue and not -iProdValue and now there is only one place to change it and not 11) and dynamically adapts to mods adding/removing national wonders or making civ specific versions of national wonders.
 
Hey, sounds useful, but I was wondering if you (or anyone) could help me with creating a table in a similar way with all Naval Units (including modded ones, so according to the last method). The trait I want to use it for involves getting a bonus when a city is training Naval Units. I intend to use city:GetProductionUnit(), which should return the UnitType of the Naval Units. I can't seem to successfully use these methods to recreate this table without any randomness factor to it. I'm using the PlayerDoTurn event. I tried using SQL to refer to the DomainType of a Unit, but I was unsuccessful :/
 
Hey, sounds useful, but I was wondering if you (or anyone) could help me with creating a table in a similar way with all Naval Units (including modded ones, so according to the last method). The trait I want to use it for involves getting a bonus when a city is training Naval Units. I intend to use city:GetProductionUnit(), which should return the UnitType of the Naval Units. I can't seem to successfully use these methods to recreate this table without any randomness factor to it. I'm using the PlayerDoTurn event. I tried using SQL to refer to the DomainType of a Unit, but I was unsuccessful :/
Table Construction:
  • Not sure if there is a better method within the line
    Code:
    if (row.Domain == "DOMAIN_SEA") and (row.Combat > 0) then
    for only adding Naval Combat units to the table
  • You would end up with a table that would have data pairs that would look like:
    k v
    26* UNIT_MISSILE_CRUISER
    15* UNIT_NUCLEAR_SUBMARINE
    * these aren't the real ID#s used for these units, they are just numbers I filled in to show an example
Code:
tYourTableName = {}
for row in GameInfo.Units() do
	if (row.Domain == "DOMAIN_SEA") and (row.Combat > 0) then
		tYourTableName[row.ID] = row.Type
	end
end
Snippet of code I would start with in a PlayerDoTurn:
Code:
function NavalUnits(iPlayer)
	local pPlayer = Players[iPlayer]
	for pCity in pPlayer:Cities() do
		if pCity:IsCoastal() and (pCity:GetProductionUnit() ~= -1) then
			if tYourTableName[pCity:GetProductionUnit()] then
				--something happens here when the unit is listed in the table
			end
		end
	end
end
GameEvents.PlayerDoTurn.Add(NavalUnits)
Anything different there than what you were trying?

Spoiler :
  • Ima still a big struggle when trying to create functional direct querry methods within lua lines such as:
    Code:
    for row in GameInfo.Units() do
    no matter how mucha I study the whoward tutorials and references
  • Ima understand the concepts, Ima just always a screwin' up tha code
 
Code:
for row in GameInfo.Units() do
  if (row.Domain == "DOMAIN_SEA") and (row.Combat > 0) then
    tYourTableName[row.ID] = row.Type
  end
end

what this is doing is asking the game to find every unit in the Units table and then discarding all of the ones that arn't DOMAIN_SEA or arn't a combat unit, which is not that efficient.

GameInfo.Units() is equivalent to the SQL
Code:
SELECT * FROM Units;
which includes another inefficiency ... the Units table contains over 90 columns (depending on game version) all of which will be added to the row table, but we only use two of them.

What we actually want is the SQL
Code:
SELECT ID, Type FROM Units WHERE Combat > 0 AND Domain = 'DOMAIN_SEA';
which only retrieves the two columns used and also includes the conditions, so only selects the units required

Firaxis provide the DB global for accessing the database and the method Query() for executing a SQL statement directly, so we can change the code above to

Code:
for row in DB.Query("SELECT ID, Type FROM Units WHERE Combat > 0 AND Domain = 'DOMAIN_SEA'") do
  tYourTableName[row.ID] = row.Type
end
 
Thanks both! I didn't know what to put between the [] brackets, and I forgot to use the quotation marks in the SQL.
 
Ah, what I've been doing wrong now makes sense. For whatever reason I still just struggle like crazy with adapting the SQL commands in lua, probably because I still just struggle with SQL itself.
 
This is my first try at this, so bear with me... I have set up tables modeled somewhat as yours are. I have two linked variables in my table (excuse me if I am misusing the nomenclature), buildings id (that are located in the Capital)and cityname.

I have a feeling I am missing an important step in that I really ought to loop through the table I have constructed before naming the variables, but I am not certain how to do that... anyhow, here is my code:

Code:
--allows building of Earl units if there is a Dummy building in the capital

local iEarl = GameInfoTypes.UNIT_SS_BOOSTER
local CityBuildings = {
  {id=GameInfoTypes.BUILDING_EARLDOM_DUMMY_YORK, cityname="York"},
  {id=GameInfoTypes.BUILDING_EARLDOM_DUMMY_CORNWALL, cityname="Exeter"},
  {id=GameInfoTypes.BUILDING_EARLDOM_DUMMY_CANTERBURY, cityname="Canterbury"},
  {id=GameInfoTypes.BUILDING_EARLDOM_DUMMY_LEICESTER, cityname="Leicester"},
  {id=GameInfoTypes.BUILDING_EARLDOM_DUMMY_NORWICH, cityname="Norwich"},
  {id=GameInfoTypes.BUILDING_EARLDOM_DUMMY_WESSEX, cityname="Winchester"},
  {id=GameInfoTypes.BUILDING_EARLDOM_DUMMY_WROXETER, cityname="Wroxeter"}
 }

function CityTrainEarlUnit(iPlayer, iCity, iUnit)

	--differentiate Earl units
	if iUnit == iEarl then

		local player = Players[iPlayer]			
		local pCity = player:GetCityByID(iCity)
		local EarlBuilding = CityBuildings.id
		local EarlCity = CityBuildings.cityname

		--check that city is an eligible city
		if pCity:GetName() == EarlCity then

			-- check for dummy building in Capital
			local pCapital = player:GetCapitalCity()		
			if pCapital:IsHasBuilding(EarlBuilding) then --if yes, cannot construct
			
				return false
			else		
			
				return true	--needed when the capital does not have the dummy building so allows building		
			end
		
		else	
			return true	--allows building if city is is eligible
		end

	else return true --default needed for units NOT Earl Units

	end
end

GameEvents.CityCanTrain.Add(CityTrainEarlUnit)

I hope I am not totally out to lunch with this code :) . Like I said, it likely needs a loop to go through the buildings and look at each one separately... but I am not certain how.

Thank-you and sorry for the trouble.
 
Assuming I understand what you are looking for, and assuming I have not goofed in restructuring your table:
Code:
--allows building of Earl unit in specific cities if there is NOT a Specified Dummy building in the capital

local iEarl = GameInfoTypes.UNIT_SS_BOOSTER
local CityBuildings = { York=GameInfoTypes.BUILDING_EARLDOM_DUMMY_YORK, Exeter=GameInfoTypes.BUILDING_EARLDOM_DUMMY_CORNWALL,
	Canterbury=GameInfoTypes.BUILDING_EARLDOM_DUMMY_CANTERBURY, Leicester=GameInfoTypes.BUILDING_EARLDOM_DUMMY_LEICESTER,
	Norwich=GameInfoTypes.BUILDING_EARLDOM_DUMMY_NORWICH, Winchester=GameInfoTypes.BUILDING_EARLDOM_DUMMY_WESSEX,
	Wroxeter=GameInfoTypes.BUILDING_EARLDOM_DUMMY_WROXETER }

function CityTrainEarlUnit(iPlayer, iCity, iUnit)
	--differentiate Earl units
	if iUnit == iEarl then
		local player = Players[iPlayer]			
		local pCity = player:GetCityByID(iCity)
		if CityBuildings[pCity:GetName()] then
			--return false if the capital has the building, return true if capital does not have the building
			return (not player:GetCapitalCity():IsHasBuilding(CityBuildings[pCity:GetName()]))
		else
			return false -- disable the unit if the city is not listed in the table
		end
	end
	return true -- default to true if the unit is not an SS_BOOSTER
end

GameEvents.CityCanTrain.Add(CityTrainEarlUnit)
The real issue as I see it is if users are cantankerous and rename "York", for example, to "Bluginhame". So you'll have to be very clear to users not to rename the preset cities in the scenario.
 
The real issue as I see it is if users are cantankerous and rename "York", for example, to "Bluginhame". So you'll have to be very clear to users not to rename the preset cities in the scenario.

If this is a scenario with a pre-set .civ5map file, just find the city ID's and hard code to those and not the city names (pretty sure those are fixed by WB and not assigned by the DLL, but it is a very long time since I've done anything with WB). That way you can even create "Duke of Bluginhame" as you know he's actually "Duke of city with ID 12345 and we don't care what you name the city"
 
If this is a scenario with a pre-set .civ5map file, just find the city ID's and hard code to those and not the city names (pretty sure those are fixed by WB and not assigned by the DLL, but it is a very long time since I've done anything with WB). That way you can even create "Duke of Bluginhame" as you know he's actually "Duke of city with ID 12345 and we don't care what you name the city"
Just doing a quick check in WB in the "Cities" tab after pre-placing a city, there's nothing showing anywhere to give a CityID# in worldbuilder -- so that data would need to be 'pulled' sometime on game loading/game start and then either persisted across saves or else the map XY (which is supplied for the plot) would have to be used to get this info (in which case the scenario would have to disallow city razing).
 
I think if you get it once (eg with FireTuner) it'll be the same for every game thereafter
 
I have placed a wonder in each of the cities so that they cannot be razed.

For now, since saving persistent data is not yet in my skill set, I think advising the players not to rename cities or specific units (as I have another lua that is dependent on unit names) will do for now.

It is likely an easy process in future because the cities in my mod are all placed via lua in pregame sequence... so somewhere at that point the data is surely available to do some data saving.

In any case, when I polish my mod further down the road, I will revisit this...

Thank-you.
 
the cities in my mod are all placed via lua in pregame sequence

Which means you already know the X,Y location of the city (eg for York, iYorkX and iYorkY are fixed and known), so you don't need to persist anything

Map.GetPlot(iYorkX, iYorkY).GetPlotCity()

is all you need
 
Top Bottom