View Full Version : Thal's Modding Utilities


Thalassicus
Nov 14, 2010, 01:08 AM
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 ").


Lua Logger
Events
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:

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:


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


include( "TU_LuaLogger.lua" )
local logger = Game.LuaLogger:New()
logger:SetLevel("DEBUG")
logger:Info("Loading ThalsUtilities")Valid output levels are similar to log4j:

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.

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.
.
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.
.
INFO
Interesting events like starting a function.
Example: A map script prints results of natural wonder placement.
.
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.
.
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:

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
endIf the logger level is set to DEBUG and the condition holds true, this will display in the Live Tuner console:

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)

robk
Nov 15, 2010, 11:39 AM
mmmm, delicious. Thanks for taking the time to do this :goodjob:

Deep_Blue
Nov 27, 2010, 06:26 AM
Thanks for these very useful utilities.

About Lua Logger what is it different from using print function in the code itself? it seems to me similar a print to tuner function.

bryanw1995
Nov 27, 2010, 09:10 AM
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!

Thalassicus
Nov 30, 2010, 05:38 AM
@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).

Zyxpsilon
Dec 03, 2010, 08:32 AM
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.

Thalassicus
Dec 03, 2010, 09:30 AM
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.

Deep_Blue
Dec 03, 2010, 11:47 AM
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.

Zyxpsilon
Dec 03, 2010, 04:33 PM
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.

Zyxpsilon
Dec 04, 2010, 10:09 AM
Shouldn't the compatibility util be applied to DiploCorner.LUA as well since InfoAddict & DK's CivRanking slots are conflicting?

Thalassicus
Dec 04, 2010, 11:29 PM
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.

marekb
Dec 05, 2010, 02:34 AM
Is it possible to merge this mod with Growth Notifications (http://forums.civfanatics.com/showthread.php?t=400492) mod by alpaca?

Zyxpsilon
Dec 05, 2010, 10:38 AM
Shooottsss... but, when dust settles (and the new patch is finally out!) we'll probably gain better opportunities to look deeper into such issues. Patience is key, i guess. Knowledge, an absolute must have.

robk
Dec 06, 2010, 11:28 PM
Hiya Thal,

The latest version (http://forums.civfanatics.com/showthread.php?p=9981900&postcount=135) 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

gruven
Dec 07, 2010, 06:21 AM
Is it possible to merge this mod with Growth Notifications (http://forums.civfanatics.com/showthread.php?t=400492) mod by alpaca?

That would be handy. I tried using them both together without merging and now I get no growth notifications at all. :( I personally didn't look into how to merge them yet, but I may be able to this weekend.

Thalassicus
Dec 12, 2010, 04:58 AM
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.

Thalassicus
Feb 24, 2011, 07:40 PM
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.

tyetibbs
Mar 25, 2011, 06:36 PM
is it just me or are colloseums only giving 1 happiness? EDIT: lol nevermind, happiness limit of the population my bad.

Thalassicus
Aug 02, 2011, 07:43 AM
I've updated the documentation and download for this utility library.

apollyon
Oct 09, 2011, 02:29 AM
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

Thalassicus
Oct 09, 2011, 03:29 AM
Instructions are included:

http://forums.civfanatics.com/attachment.php?attachmentid=303882&stc=1&d=1318152481

Links to here (http://forums.civfanatics.com/showthread.php?p=10878403#post10878403).

Animations can be re-enabled by placing an "_" underscore at the front of this file name:

\CiVUP - Civ V Unofficial Patch\Core\_CiVUP - Quick Combat.sql

apollyon
Oct 09, 2011, 01:24 PM
thanks, I missed that

Thalassicus
Feb 15, 2012, 05:16 AM
For modders, in v131 the Save/Load savegame storage functions now accept formatting (like string.format). The position of the key and value arguments were swapped in Save functions to accommodate this. I also added a generic SaveValue and LoadValue. Here are examples of using this feature:

local someKey = 10
SaveValue(10, "someKey")
LoadValue("someKey")

MapModData.VEM.Events[playerID].Chance =
LoadValue("MapModData.VEM.Events[%s].Chance", playerID)

SaveValue(
MapModData.VEM.Events[playerID].Chance,
"MapModData.VEM.Events[%s].Chance",
playerID
)

MapModData.VEM.Events[playerID].HasOccured[eventID][plotID] =
(1 == LoadValue(
"MapModData.VEM.Events[%s].HasOccured[%s][%s]",
playerID,
eventID,
plotID
)
)This makes it much easier to write save/load code.


Copy-paste a reference.
Drag variable names from [ ] to the end.
Add %s to the empty [ ].