Thal's Modding Utilities

Thalassicus

Bytes and Nibblers
Joined
Nov 9, 2005
Messages
11,057
Location
Texas
This is a library of Lua utilities I created as part of the Civ V Unofficial Patch. To get the library, download CiVUP by searching the Civ V Mod Browser for the Unofficial Patch and Vanilla Enhanced package (Main Menu > Mods > Browse Mods, search for " civup ").

  1. Lua Logger
  2. Events
  3. Helper functions



- Lua Logger -

While working on a mod it's often useful to print information to the console when fixing bugs. This utility is a powerful extension of the standard print function.

  • Based on the industry standard log4j testing framework.
  • Your mod won't spam the lua console when other modders need to use it.
  • Automatically converts invalid variables to strings (nil outputs as "nil"), eliminating the need to tostring(...) variables that might not be initialized.
  • Testers of your mod can easily enable specific levels of debug output without any programming knowledge, providing you valuable feedback.
  • Built-in support for string.format codes to customize your debug output:
PHP:
logger:Info("GiveMilitaristicRewards %15s rateMod=%.2f  turnRate=%i",
             player:GetName(), rateMod, turnRate)


output:
myModName: INFO:   GiveMilitaristicRewards         Augustus 1.21  6
myModName: INFO:   GiveMilitaristicRewards   Nebuchadnezzar 1.76  3
myModName: INFO:   GiveMilitaristicRewards        Elizabeth 1.53  4
To use this utility:

  1. Add TU_LuaLogger.lua to your project
  2. Set "Import Into VFS" to True for TU_LuaLogger.lua.
  3. Include the file at the top of your code.
  4. Create a logger.
  5. Set output level.
  6. Send the logger messages.

PHP:
include( "TU_LuaLogger.lua" )
local logger = Game.LuaLogger:New()
logger:SetLevel("DEBUG")
logger:Info("Loading ThalsUtilities")
Valid output levels are similar to log4j:
  1. FATAL
    Critical errors where the mod cannot continue execution. Sending a Fatal message to the logger prints the message, terminates execution of the active Lua thread, and prints a trace of function calls which led to the error.
    Example: A function expects an integer for one of its parameters but receives a nil value.
  2. ERROR
    Other errors or unexpected conditions, but the mod can continue processing.
    Example: A map script attempted to place 10 resources, but could only find locations for 9 resources.
    .
  3. WARN
    'Almost' errors or undesirable situations, but not necessarily "wrong".
    Example: A utility library for save-data warns users of the library that the map cache cannot be shared between mods.
    .
  4. INFO
    Interesting events like starting a function.
    Example: A map script prints results of natural wonder placement.
    .
  5. DEBUG
    Detailed information on the flow through the system.
    Example: A function prints the quantity of each terrain type around a unit before performing actions based on the surrounding terrain.
    .
  6. TRACE
    Step-by-step execution path of instructions, since the ModBuddy compiler can't do that for Lua.
    Example: For every building, a function prints the yield type and quantity of every policy each player has which improves those buildings.

These are listed from most to least important. If the logger level is set to "ERROR", only fatal and error messages will display. At the "TRACE" level, all messages will display. These messages appear in the Tuner console.

Say you want to display someVariable, then check if it equals a test value:

PHP:
include( "ThalsUtilities" )
local logger = Game.LuaLogger:New()
logger:SetLevel("DEBUG")

function someFunction(someVariable)
  logger:Debug("someVariable = %s", someVariable);
  if (someVariable ~= 42) then
    logger:Warn("someVariable is not the answer to life.");
  end
end
If the logger level is set to DEBUG and the condition holds true, this will display in the Live Tuner console:

PHP:
myModName: DEBUG:  someVariable = 10
myModName: WARN:   someVariable is not the answer to life.
When you're ready to release your mod, you can change setLevel("DEBUG") to setLevel("ERROR") and nothing will display but critical error messages.

- Events -

\TU_LuaEvents.lua
LuaEvents.TU_Initialize()
LuaEvents.ActivePlayerTurnStart_Turn()
LuaEvents.ActivePlayerTurnStart_Player(player)
LuaEvents.ActivePlayerTurnStart_Unit(unit)
LuaEvents.ActivePlayerTurnStart_City(city, owner)
LuaEvents.ActivePlayerTurnStart_Plot(plot)
LuaEvents.ActivePlayerTurnEnd_Turn()
LuaEvents.ActivePlayerTurnEnd_Player(player)
LuaEvents.ActivePlayerTurnEnd_Unit(unit)
LuaEvents.ActivePlayerTurnEnd_City(city, owner)
LuaEvents.ActivePlayerTurnEnd_Plot(plot)
LuaEvents.NewCity(hexPos, playerID, cityID, cultureType, eraType, continent, populationSize, size, fowState)
LuaEvents.NewUnit(playerID, unitID, hexVec, unitType, cultureType, civID, primaryColor, secondaryColor, unitFlagIndex, fogState, selected, military, notInvisible)
LuaEvents.NewImprovement(hexX, hexY, cultureArtID, continentArtID, playerID, engineImprovementTypeDoNotUse, improvementID, engineResourceTypeDoNotUse, resourceID, eraID, improvementState)
LuaEvents.PlotAcquired(plot, newOwnerID)
LuaEvents.PolicyAdopted(policyID, isPolicy)
LuaEvents.CityOccupied(city, player, isForced)
LuaEvents.CityPuppeted(city, player, isForced)
LuaEvents.CityLiberated(city, player, isForced)
LuaEvents.PromotionEarned(unit, promotionType)
LuaEvents.UnitUpgraded(unit)
LuaEvents.BuildingConstructed(player, city, buildingID)
LuaEvents.BuildingDestroyed(player, city, buildingID)
LuaEvents.CheckPlotBuildingsStatus(plot)

- Helper Functions -

\TU_Utils.lua
Game.DoOnce(str)
Game.DeepCopy(object)
Game.Literalize(str)
Game.GetTruthTableResult(truthTable, inputs)
Game.IsBetween(lower, mid, upper)
Game.Constrain(lower, mid, upper)
Game.Round(num, places)
RoundDown(num, idp)
Game.Shuffle(t)
Game.Reverse(t)
Game.Contains(list, value)
Game.GetMaximum(list)
Game.GetRandomWeighted(list, size)
Game.RemoveExtraNewlines(str)

\TU_LoadSave.lua
LoadValue(...)
SaveValue(value, ...)
LoadPlayer(player, ...)
SavePlayer(player, value, ...)
LoadCity(city, ...)
SaveCity(city, value, ...)
LoadUnit(unit, ...)
SaveUnit(unit, value, ...)
LoadPlot(plot, ...)
SavePlot(plot, value, ...)

\TU_Initialize.lua
Events.LoadScreenClose.Add(function() MapModData.VEM.Initialized = true end)

\Core\TU_City.lua
City_GetBestPlotPurchaseCity(city, plot)
City_GetBuildingsOfFlavor(city, flavorType, budget, includeWonders)
City_GetUnitsOfFlavor(city, flavorType, budget)
City_GetBestBuildableUnit(city, flavorType, excludeSea)
City_GetBuildableUnitIDs(city)
City_GetID(city)
Map_GetCity(cityID)
City_GetNumBuilding(city, building)
City_GetNumBuildingClass(city, buildingClass)
City_GetPurchaseCost(city, itemTable, itemID)
City_GetUnitExperience(city, unitType)
City_IsBuildable(city, buildingID, continue, testVisible, ignoreCost)
City_IsPurchaseable(city, testVisible, unitID, buildingID, projectID)
City_SetResistanceTurns(city, turns)

\Core\TU_Misc.lua
Game.GetAverageHumanEra()
Game.GetAverageHumanHandicap()
Game.GetActiveHuman()
Game.GetResourceIDsOfUsage(usageType)
Game.GetSortedResourceList(sort, reverseNames)
Game.GetSpeedYieldMod(yieldID)
UI_StartDeal(arg)
Plots(sort)
Game.HasValue(conditionList, tableName)
Game.GetValue(valueName, conditionList, tableName)
Building_IsWonder(buildingType)
Improvement_GetBuildInfo(improvementType)

\Core\TU_Player.lua
You can use these functions in the format "player:GetBuildingAddonLevel(buildingID)"

PlayerClass.GetBuildingAddonLevel(player, buildingID)
PlayerClass.GetCapitalCity(player)
PlayerClass.GetDeals(player)
PlayerClass.GetPossibleDeals(player)
PlayerClass.GetMinorApproach(player, approachType)
PlayerClass.GetRivalInfluence(player, minorCiv)
PlayerClass.GetPurchaseCostMod(player, baseCost, hurryCostMod)
PlayerClass.GetResourceQuantities(player, resID)
PlayerClass.GetCitiesDemandingResource(player, resourceID)
PlayerClass.GetTraitInfo(player)
PlayerClass.GetPersonalityInfo(player)
PlayerClass.GetTurnAcquired(player, city)
PlayerClass.SetTurnAcquired(player, city, turn)
PlayerClass.GetUniqueUnitID(player, classType)
PlayerClass.GetUniqueBuildingID(player, classType)
PlayerClass.GetMinorYieldString(minorCiv, showDetails)
PlayerClass.GetCitystateThresholdString(minorCiv)
PlayerClass.HasTech(player, tech)
PlayerClass.GetImprovableResources(player)
PlayerClass.ImproveResources(player, plotList)
PlayerClass.SetHasTech(player, tech, isResearched)
PlayerClass.HasBuilding(player, building)
PlayerClass.InitUnitType(player, unit, plot, exp)
PlayerClass.InitUnitClass(player, unitClassType, plot, exp)
PlayerClass.IsAliveCiv(player)
PlayerClass.IsMilitaristicLeader(player)
PlayerClass.IsAtWarWithHuman(player)
PlayerClass.IsAtWarWithAny(player)
PlayerClass.EverAtWarWithHuman(player)
PlayerClass.HasMet(player, otherPlayer)
PlayerClass.IsAtWar(player, otherPlayer)
PlayerClass.IsAtPeace(player, otherPlayer)
PlayerClass.SetFriendship(minorCiv, majorCivID, friendship)

\Core\TU_Plot.lua
Plot_BuildImprovement(plot, improveID)
Plot_Buy(plot, player, city, cost)
Plot_GetCost(city, plot)
Plot_FindPlotType(startPlot, plotType)
Plot_GetCombatUnit(plot)
Plot_GetAreaWeights(plot, minR, maxR)
Plot_GetID(plot)
Plot_GetNearestOceanPlot(centerPlot, maxRadius, minArea)
Plot_GetPlotsInCircle(plot, minR, maxR)
Plot_IsFlatDesert(plot)

\Core\TU_Unit.lua
Unit_GetClass(unit)
Unit_IsCombatDomain(unit, domain)
Unit_IsWorker(pUnit)
Unit_CanUpgrade(unit, newID, budget)
Unit_GetXPStored(unit)
Unit_GetXPNeeded(unit)
GetExperienceForLevel(level)
Unit_Replace(oldUnit, unitClass)
Unit_ReplaceWithType(oldUnit, unitType)
 
I don't do any modding, but I definitely take advantage of your great mods. I'm not exaggerating when I say that you've made a HUGE difference in this game. Firaxis should hire you full time!
 
@Deep_Blue
The difference is these methods provide an easy way to disable all the print statements (by changing the output level). Otherwise you'd have to comment out every single print statement, or do an if-then for each one, either of which would be more time consuming. Leaving lots of print statements in one's mod makes it more difficult for other modders to use the tuner (have to disable yours to stop output clutter).
 
A tricky "request", please.

FireTuner is kinda useful when some analysis must be done while we're testing our mods and yet, as usual, it must be ALT_tabbed back & forth to get anything done.

So, you may just know how such a feature could be added to the InGame assets or controllable panels;

-- Minimize it to an always_on_top_Floating_Icon (possibly hovering underneath the Diplo tab in the right corner) & simply click to re-use or de-activate when done.

Any chance you might find time to look into this issue - for me or anyone else interested?

PS; Actually, there are a number of additional functionality that could be integrated in such a "virtual" button, if you can extrapolate - as i do - some extra (but weird!) principles based on the fact it would be there only to exploit.
 

Attachments

  • firetuner_button.png
    firetuner_button.png
    8.2 KB · Views: 3,250
I'm not really as familiar with the UI as I am with data edits and lua scripting in general. There's a lot of odd design patterns the developers followed when creating the UI.
 
FireTuner is kinda useful when some analysis must be done while we're testing our mods and yet, as usual, it must be ALT_tabbed back & forth to get anything done.

When using tuner run civ5 in windowed mode it will be easier to switch between the two applications. I also reduce graphics to minimum while testing to make things faster.
 
On a 1680x1050 monitor, it's almost painful to be forced into some low-res windowed-mode either by choice or obligation... which i've used already-duh... i'd rather find (or recommend) an indirect solution such as the minimize_to_button described above.
 
Well, all UI mods require merging but that's something a little more complicated. The thing about ingame / notifications is you just append new material without any coding expertise required.
 
Hiya Thal,

The latest version of Info Addict removes the lua context from InGame.xml. Thanks for putting the reference in there but, from v8 on, it won't be necessary :D
 
For now, you can just remove the NotificationPanel.lua file from this mod and they should be compatible. I'm not actually using it yet... haven't gotten around to sitting down and working out that notification system.
 
I've updated these utilities significantly in the past month, and now updated the documentation here in this thread. You can also see the same usage examples in the ThalsUtilities.lua file itself.

Something that might be helpful are events to run through all units/plots/etc once per turn.
 
is it just me or are colloseums only giving 1 happiness? EDIT: lol nevermind, happiness limit of the population my bad.
 
How do I re-enable animated combat? I just discovered I've been creamed by air attacks the last 5-10 turns. I thought it was a new stealth unit. I love the mod and and all but where is the goddamn toggle
 
Top Bottom