whoward69
DLL Minion
Consider the following code
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
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
and our code then picks a "line" at random from the table and feeds it into a generic code block
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
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
with
(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
with the associated change to
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
so we can replace our hard-coded nationalWonders table with
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.
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
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
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"}
}
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
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
Code:
local iRandomNationalWonderTitle = GameInfo.Buildings[iRandomNationalWonder].Description
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
}
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;
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.