Lua Objects

Thanks a lot for the detailed answers LeeS! But if GetCulturalProgress() exists in this context then I have no idea what my problem actually is. This is the error message I'm getting:
Code:
Runtime Error: C:/Users/me/Documents/My Games/Sid Meier's Civilization VI/Mods/Myopic test mod/GetResearch.lua:37: function expected instead of nil
stack traceback:
    C:/Users/me/Documents/My Games/Sid Meier's Civilization VI/Mods/Myopic test mod/GetResearch.lua:37: in function 'OnTurnBegin'
    [C]: in function 'func'
    [C]: in function '(anonymous)'

And this is my Lua code: (line 37 is the line with GetCulturalProgress)
Code:
function OnTurnBegin(...)
    local args = {...};

    if args[1] > 0 then
        return;
    end

    print("TESTMOD:    TurnBegin event");
    
    local localPlayer        = Players[Game.GetLocalPlayer()];
    if (localPlayer == nil) then
        return;
    end

    local playerTechs           = localPlayer:GetTechs();
    local currentTechID :number = playerTechs:GetResearchingTech();

    if currentTechID >= 0 then
        local progress  :number = playerTechs:GetResearchProgress(currentTechID);
        local cost      :number = playerTechs:GetResearchCost(currentTechID);
        print("TESTMOD:    Research progress "..progress/cost);
    else
        print("TESTMOD:    No tech being researched");
    end

    local playerCivics           = localPlayer:GetCulture();
    local currentCivicID :number = playerCivics:GetProgressingCivic();

    print("TESTMOD: " .. currentCivicID)

    if currentCivicID >= 0 then
        local turns    :number = playerCivics:GetTurnsLeftOnCurrentCivic(currentCivicID);
        local yield    :number = playerCivics:GetCultureYield(currentCivicID);
        local cost     :number = playerCivics:GetCultureCost(currentCivicID);
        print("TESTMOD:    turns "..turns .. " yield " ..yield);
        print("TESTMOD:    Civics progress ".. 1-yield*turns/cost);
        local progress :number = playerCivics:GetCulturalProgress(currentCivicID);
        print("TESTMOD:    Civics absolute progress ".. progress);
    else
        print("TESTMOD:    No civics being researched");
    end

    print("TESTMOD:    end of function");
end

Events.PlayerTurnActivated.Add( OnTurnBegin )
I know that the playerCivics variable isn't nil since it returns correct answers for the turns/yield/cost variables. So I assumed that GetCulturalProgress() wasn't defined here. But if it's not that, then what does the error mean?

BTW, my mod is loaded in-game using the Additional Content menu.
 
Yeah, the GetCulturalProgress method now appears to be dead. I am as sure as I can be it was working before the Patch. But I know I also have not tried to set "YIELD_CULTURE" as a key in the table I am using since the patch was released. Gee whiz, thank you Firaxis.
 
OK, thanks. At least it's not just me going crazy. Then consider my code in the last post as a contribution, since it provides a workaround way of approximating the progress to the current civic. But since it's based on the turns-to-completion function it will sometimes be off by around a turn worth of culture.
 
Quick question: how do I trigger an existing notification type from Lua? I tried this but nothing seems to happen, no error message either:
Code:
LuaEvents.ActionPanel_ActivateNotification(GameInfo.Notifications["NOTIFICATION_BARBARIANS_SIGHTED"]);
BTW, is it OK to post stupid Lua questions here? There isn't a separate SDK/Lua forum yet (as there is for Civ 5), and this seems to be where the Lua people hang out.
 
Last edited:
@LeeS, are the setter methods like ChangeCurrentCulturalProgress() still exposed in the GameplayScripts context? I moved to using UserInterface with the InGame context since GameplayScripts are broken on reload, and it looks like nearly all of the setter methods have been removed from that context. I can still access them in the tuner in the GameCore_Tuner context, but trying to use that context in .modinfo doesn't work (the lua files never get executed).

They seem to be removing everything they don't use in their own Lua UI from the InGame context--I was thinking it was just the setters, but if they also removed a getter like GetCulturalProgress(), the only rationale I can see is if they aren't using it, hide it.

I was wanting to create a district project to add influence points towards earning envoys, but even in the GameCore_Tuner context, there's a getter for influence points but not a setter. There is a setter for total unused envoys and for granting a free envoy to a city state, but both of those have been hidden from the InGame context.

In IDA I can see the functions adding methods to the Lua objects for different contexts, it wouldn't be terribly difficult to hack a modded dll that exposed all the methods of every object in every context, but without the source code for the dll, I'd have to do it all over again every time they patch the dll, which would take a few hours each time. If they don't give modders some loving soon, though, I might just do that, and post the modded dll as a mod component everyone could use as a dependency. They are really hamstringing us and seem to be pushing for gameplay mods to have to rely solely on what can be done in the database, with Lua strictly for read-only UI displays.
 
I am saying 'as of a couple days ago' because I took a little vacation over the weekend from trying to mod lua in civ6. All of the following though is in Gameplay context. It does not suprise me that a 'Change' or 'Set' method is not working in a UI context, as those are almost all for display of information rather than alteration of in-game data, or for selecting policy cards, techs to research, and the like rather than directly changing a player's Faith Score for example.
  1. pCulture:SetCulturalProgress(iCivic, iIntChange) is working as of a couple days ago
  2. pCulture:ChangeCurrentCulturalProgress(iIntChange) not sure of as I have not tried it since we found that GetCulturalProgress does not work and I rewrote to not neeed using it, or attempting to determine a player's progress toward a civic.
  3. pPlayerTechs:GetResearchingTech() is working as of a couple days ago
  4. pPlayerTechs:GetResearchCost(iTech) is working as of a couple days ago
  5. pPlayerTechs:SetResearchProgress(iTech, iTechCost) is working as of a couple days ago
  6. pPlayerTechs:ChangeCurrentResearchProgress(iChange) I am not sure of because I have not tried I think since the patch.
  7. playerReligion:ChangeFaithBalance(iChange) is working as of a couple days ago
  8. pTreasury:ChangeGoldBalance(iChange) is working as of a couple days ago
From what I've heard from people who are using methods similar to Gedemon's for getting lists of events and lua functions, it is not so much hiding-away as only implementing as needed in the contexts where specifically needed. Since everything else I had been using before the patch aside from GetCulturalProgress still seems to be working in Gameplay script context, I think it may have been more of an 'oops' than a 'let's go ahead and turn all of this stuff off'
 
I've dumped this before the patch, and GetCulturalProgress wasn't found in the script context (could be a problem with my code if you were able to use it)

I didn't run it again since the patch, as some "get" function reports an error now when they're called with the wrong argument and I would have to rewrite it, but it would be interesting to be able to compare.

But yeah, I do hope that script unable to reload is a bug and not a deliberate limitation to game play mods for whatever reasons.
 
@LeeS, are you actually using GameplayScripts for anything other than initial game setup? If so, how are you getting around the reload issue?
 
I'm not getting around the reload issue. I am experimenting around with stuff at the "initialization" and in hopes that one day someday lua will actually work.

I don't think I've actually finished a game yet: I've only experimented around with effects, then reworked code, then rinse and repeat. :lol:
 
Not sure if this is the right thread for this, but has anyone been able to determine if the effects from GameEffects.xml can be modified? Or new ones added? I see some data for GameEffects but it doesn't seem to map to the effects defined in GameEffects.xml.
 
Some info on game engine event arguments:

Events.CityMadePurchase(iPlayer, CityID, PlotX, PlotY, ItemTypeReference, ItemID)
  1. Fires when the city purchases an item
  2. I've not had opportunity to check for anything except purchasing by gold
    • not clear whether ItemTypeReference is to a XML table ID or some other internal ID#
      • See @PlotinusRedux explanation of what it actually is below.
        ItemTypeReference refers to the EventSubType hash values. I.e., "if ItemTypeReference == EventSubTypes.UNIT then ..."
        Updated the sample code accordingly.
    • ItemTypeReference = -660405657 for Buildings
    • ItemTypeReference = 366026264 for Units
    • ItemTypeReference = 2010226114 for Plots, and in which case PlotX, PlotY will reference the plot that was purchased.
    • PlotX, PlotY will likely also reference the plot where the "building" is located when a district-related building is purchased, but I have not as yet verified.
Code:
function CityMadePurchaseListener(iPlayer, CityID, PlotX, PlotY, ItemTypeReference, ItemID)
	print("[CityMadePurchaseListener] iPlayer = " .. iPlayer)
	print("[CityMadePurchaseListener] CityID = " .. CityID)
	print("[CityMadePurchaseListener] PlotX = " .. tostring(PlotX))
	print("[CityMadePurchaseListener] PlotY = " .. tostring(PlotY))
	print("[CityMadePurchaseListener] ItemTypeReference = " .. ItemTypeReference)
	print("[CityMadePurchaseListener] ItemID = " .. ItemID)
	local pPlot = Map.GetPlot(PlotX, PlotY)
	local iPlotID = pPlot:GetIndex()
	if (ItemTypeReference == EventSubTypes.BUILDING) then
		print("[CityMadePurchaseListener] Purchased ItemID was a building: " .. GameInfo.Buildings[ItemID].BuildingType)
	elseif (ItemTypeReference == EventSubTypes.UNIT) then
		print("[CityMadePurchaseListener] Purchased ItemID was a unit: " .. GameInfo.Units[ItemID].UnitType)
	elseif (ItemTypeReference == EventSubTypes.PLOT) then
		print("[CityMadePurchaseListener] Purchased ItemID was a Plot")
		print("[CityMadePurchaseListener] iPlotID = " .. iPlotID)
	else
		print("[CityMadePurchaseListener] Purchased ItemID was of an unknown item-type")
	end
	print("  ")
	local pCity = Players[iPlayer]:GetCities():FindID(CityID)
	local sCityName = Locale.Lookup(pCity:GetName())
	print("[CityMadePurchaseListener] CityName = " .. sCityName)
	print("  ")
end
Events.CityMadePurchase.Add(CityMadePurchaseListener)
Events.CityInitialized(PlayerID, CityID)
  1. Fires when a city is founded (initialized)
  2. there seems to be another very similar game engine event but I'm not sure of the difference as yet
Code:
function OnCityInitialized(PlayerID, CityID)
	local pPlayer = Players[PlayerID];
	if pPlayer:IsHuman() and (pPlayer:GetCities():GetCapitalCity() ~= nil) then
		local pCity = pPlayer:GetCities():FindID(CityID)
		local sCityName = Locale.Lookup(pCity:GetName())
		print("  ")
		print("[OnCityInitialized] CityName = " .. sCityName)
		print("  ")
	end
end
Events.CityInitialized.Add(OnCityInitialized);
Events.PlayerTurnActivated( iPlayer, bIsFirstTimeThisTurn )
  1. Fires at the start of every player's turn
  2. fires for the human player at game set-up as soon as they press the spinning globe button. this is because at this point their turn has been 'activated'
  3. as a practical matter bIsFirstTimeThisTurn will always be true unless you call the function from some other event and pass 'false' for the 2nd argument
Code:
local iExtraFreeBuilding = GameInfo.Buildings["BUILDING_MONUMENT"].Index
function GiveFreeMonuments( iPlayer, bIsFirstTimeThisTurn )
	print("GiveFreeMonuments: bIsFirstTimeThisTurn is " .. tostring(bIsFirstTimeThisTurn))
	print("GiveFreeMonuments: iPlayer is " .. tostring(iPlayer))
	local pPlayer = Players[iPlayer]
	if pPlayer:IsHuman() and (pPlayer:GetCities():GetCapitalCity() ~= nil) then
		print("GiveFreeMonuments: Player is Human and Capital City has been founded")
		local pCities:table = pPlayer:GetCities();
		for i, pCity in pCities:Members() do
			local sCityName = Locale.Lookup(pCity:GetName())
			if not pCity:GetBuildings():HasBuilding(iExtraFreeBuilding) then
				print("GiveFreeMonuments: " .. sCityName .. " Did not have a monument in the city. It was placed in the city")
				local pCityPlot = Map.GetPlot(pCity:GetX(), pCity:GetY())
				local pCityBuildQ = pCity:GetBuildQueue()
				pCityBuildQ:CreateIncompleteBuilding(iExtraFreeBuilding, pCityPlot:GetIndex(), 100);
			else
				print("GiveFreeMonuments: " .. sCityName .. " had a monument in the city. None was needed.")
			end
		end
	else
		print("GiveFreeMonuments: Player is not Human or Capital City has not been founded")
	end
end
Events.PlayerTurnActivated.Add(GiveFreeMonuments)
Events.ImprovementAddedToMap(PlotX, PlotY, ImprovementID, PlayerID, ResourceID, Unknown1, Unknown2)
  1. fires when any improvement is added to the map, including Barbarian camps, etc.
  2. You really need to code along these lines:
    Code:
    function OnImprovementAddedToMap(PlotX, PlotY, ImprovementID, PlayerID, ResourceID, Unknown1, Unknown2)
    	if (PlayerID ~= 63) and (PlayerID ~= -1) then
    		print("  ")
    		print("OnImprovementAddedToMap: PlotX = " .. tostring(PlotX))
    		print("OnImprovementAddedToMap: PlotY = " .. tostring(PlotY))
    		print("OnImprovementAddedToMap: ImprovementID = " .. tostring(ImprovementID))
    		print("OnImprovementAddedToMap: PlayerID = " .. tostring(PlayerID))
    		print("OnImprovementAddedToMap: ResourceID = " .. tostring(ResourceID))
    		print("OnImprovementAddedToMap: Unknown1 = " .. tostring(Unknown1))
    		print("OnImprovementAddedToMap: Unknown2 = " .. tostring(Unknown2))
    		print("  ")
    	end
    end
    function OnLoadScreenClose()
    	Events.ImprovementAddedToMap.Add(OnImprovementAddedToMap)
    end
    Events.LoadScreenClose.Add(OnLoadScreenClose)
    Because ImprovementAddedToMap fires for every barb camp, plantation, etc., placed on the map as part of setting up a game OR reloading a saved game
  3. Events.LoadScreenClose() is a game engine event that fires when the player presses the blue 'button' with the globe once a new game or a saved game is ready to be played. (for those coming from Civ4 and unfamiliar with what this event is and when it fires)
  4. by activating the event in this way, you will avoid the event re-firing during a load of a saved game for all the improvements that were on the map when the game was saved, and you avoid having the event fire during new game start-up for every barb camp or other 'fake' improvement placed on the map (such as a goody reward hut) as part of starting a new game.

Events.ImprovementRemovedFromMap(PlotX, PlotY, PlayerID)
  1. fires when any improvement is removed from the map (like from a Builder action) but unfortunately does not tell you which improvement type (plantation, camp, etc.) was removed from the map
Code:
function OnImprovementRemovedFromMap(PlotX, PlotY, PlayerID)
	if (PlayerID ~= 63) and (PlayerID ~= -1) then
		print("  ")
		print("OnImprovementRemovedFromMap: PlotX = " .. tostring(PlotX))
		print("OnImprovementRemovedFromMap: PlotY = " .. tostring(PlotY))
		print("OnImprovementRemovedFromMap: PlayerID = " .. tostring(PlayerID))
		print("  ")
	end
end
Events.ImprovementRemovedFromMap.Add(OnImprovementRemovedFromMap)
 
Last edited:
Some info on game engine event arguments:

Events.CityMadePurchase(iPlayer, CityID, PlotX, PlotY, ItemTypeReference, ItemID)
  1. Fires when the city purchases an item
  2. I've not had opportunity to check for anything except purchasing by gold
    • not clear whether ItemTypeReference is to a XML table ID or some other internal ID#
    • ItemTypeReference = -660405657 for Buildings
    • ItemTypeReference = 366026264 for Units
ItemTypeReference refers to the EventSubType hash values. I.e., "if ItemTypeReference == EventSubTypes.UNIT then ..."

Your options are:

Code:
> p(EventSubTypes)
 InGame: FOUND_CITY
 InGame: DISTRICT
 InGame: CREATE_WONDER
 InGame: REMOVE
 InGame: REPAIR
 InGame: PLOT
 InGame: HARVEST_RESOURCE
 InGame: DAMAGE
 InGame: REMOVE_FEATURE
 InGame: ADD
 InGame: SPREAD_RELIGION
 InGame: PROJECT
 InGame: CREATE_DISTRICT
 InGame: PILLAGE
 InGame: CREATE_GREAT_WORK
 InGame: PLANT_FOREST
 InGame: CONVERT_BARBARIAN
 InGame: CLEAR_CONTAMINATION
 InGame: LAUNCH_INQUISITION
 InGame: FOUND_RELIGION
 InGame: REMOVE_HERESY
 InGame: EXTRACT_ARTIFACT
 InGame: EVANGELIZE_BELIEF
 InGame: CREATE_TRADE_ROUTE
 InGame: CREATE_ROUTE
 InGame: PILLAGED
 InGame: CREATE_IMPROVEMENT
 InGame: UNIT
 InGame: VISIBILITY
 InGame: BUILDING
 InGame: COMPLETION
 InGame: COMBAT
> print(EventSubTypes.UNIT)
 InGame: 366026264

Found it by grepping for "CityMadePurchase", Base/Assets/UI/StrategicValue_MapPlacement.lua[399] uses it.
 
I turned my DLL injection on lua_createtable to capture the tables being created. The result is incomplete--I need to trap a few other calls and do some better parsing, in particular nested tables are currently missing completely. However, it has all the base 2D tables you can access without qualifiers along with the hash values of the contents, so if you run into a situation like @LeeS did above of having an integer value with no clue what it refers to, you can search this for that value and likely find it.

http://qskoz.com/Civ6/Civ6LuaObjects.xml

It's way too large to just post here, but a random sample is:

Code:
    <UnitCommandTypes>
        <AIRLIFT hash="214200606"/>
        <AUTOMATE hash="1910652547"/>
        <CANCEL hash="-2034753245"/>
        <DELETE hash="-390733962"/>
        <DISTRICT_PRODUCTION hash="-657060295"/>
        <ENTER_FORMATION hash="826007045"/>
        <EXIT_FORMATION hash="1285291509"/>
        <FORM_ARMY hash="1513146093"/>
        <FORM_CORPS hash="-1021271134"/>
        <GIFT hash="488228117"/>
        <NAME_UNIT hash="1327613461"/>
        <PARAM_CITY_ID hash="1472408743"/>
        <PARAM_CITY_PLAYER hash="391084474"/>
        <PARAM_NAME hash="-268162639"/>
        <PARAM_PROMOTION_TYPE hash="-1892000086"/>
        <PARAM_UNIT_ID hash="1357779232"/>
        <PARAM_UNIT_PLAYER hash="1508505208"/>
        <PARAM_X hash="1371270564"/>
        <PARAM_Y hash="-2028798858"/>
        <PLUNDER_TRADE_ROUTE hash="316939391"/>
        <PROMOTE hash="1932162171"/>
        <STOP_AUTOMATION hash="1378751640"/>
        <TYPE hash="2087821724"/>
        <WAKE hash="813982415"/>
        <WONDER_PRODUCTION hash="-318146415"/>
    </UnitCommandTypes>
    <UnitManager>
        <CanStartCommand hash="-1719838490"/>
        <CanStartOperation hash="1381192617"/>
        <GetActivityType hash="-465130242"/>
        <GetCommandTargets hash="-1620199133"/>
        <GetEstablishInCityTime hash="-1332850817"/>
        <GetMoveToPath hash="-526506443"/>
        <GetOperationConfirmationText hash="-2030419728"/>
        <GetOperationDetailText hash="-1659186559"/>
        <GetOperationTargets hash="670716449"/>
        <GetQueuedDestination hash="-757380836"/>
        <GetReachableMovement hash="767802114"/>
        <GetReachableTargets hash="-1581081090"/>
        <GetReachableZonesOfControl hash="-225707092"/>
        <GetResultProbability hash="1351126330"/>
        <GetTimeToComplete hash="812600928"/>
        <GetTravelTime hash="1308825099"/>
        <GetTypeName hash="172085907"/>
        <GetUnit hash="-1247558796"/>
        <IsUnit hash="349149340"/>
        <RequestCommand hash="-863953180"/>
        <RequestOperation hash="1776546463"/>
    </UnitManager>
 
Hope that this is the right spot for this question. I am looking for some help with lua files in a UI context.
I have modified the ProductionPanel.lua, adding a number of my own functions along with calls to those functions from within Firaxis functions. e.g.

Code:
function MyFuncA()
  -- do something
end

function MyFuncB()
  -- do something else
end

function FiraxisFuncA

   -- firaxis code

   MyFuncA();
   MyFuncB();
end

I would like to move my functions to a new lua file "MyProductionHelper.lua" so I can cut down on the amount of code in the ProductionPanel.lua and make it easier to integrate with other mods.
So my new lua file would contain:

Code:
function MyFuncA()
  -- do something
end

function MyFuncB()
  -- do something else
end

I would still like to be able to call my functions from within the ProductionPanel.lua as if they had been declared there. I believe this would require using the include('MyProductionHelper') syntax.

1. How do I declare this new lua file "MyProductionHelper.lua" in the modinfo file?
2. Is the include() syntax enough to allow me to call the functions in the helper file or is there something somewhere else that needs to be tweaked? I have read about the VFS in Civ 5 but have found nothing regarding that in 6.

My attempts to call a function in another lua file that I have created have so far proved in vain. It may be something simple I am overlooking (probably), so any help that you can provide would be fantastic :)
 
@greyTiger , I haven't tried it myself, but someone else reported it was still not working as of the Winter Patch.

Still, you might try the following in case he was running into a load order issue:

<ImportFiles>
<Items>
<File Priority="1">YourFileToBeIncluded.lua</File>
<File Priority="0">FixarisFileWhichWillIncludeYours.lua</File>
</Items>
</ImportFiles>

If you try that, let us know if it works or not. If it doesn't, LuaEvents might be the only alternative--but of course then your functions aren't local to the Firaxis file and you'll have to pass in any locals you want to use.
 
Speaking of LuaEvents, is there somewhere an overview of all available LuaEvents to hook on to?
edit: misread

LuaEvents are not limited, you can create your own IIRC
 
And there is also GameEvents to consider with

GameEvents.YourEvent.Remove()
GameEvents.YourEvent.AccumulateINT()
GameEvents.YourEvent.TestAll()
GameEvents.YourEvent.Count()
GameEvents.YourEvent.Call()
GameEvents.YourEvent.TestAny()
GameEvents.YourEvent.Accumulate()
GameEvents.YourEvent.Add()
GameEvents.YourEvent.RemoveAll()

which works over contexts

for exemple, you add multiple function that return a value to a game event, TestAll() should return true if all of them return something, TestAny() should return true if one of them return something
 
Interesting. So the only predefined LuaEvents are those that you can already find Lua files, there are none provided by the core engine.
They should've used more of them, to make it easier to hook into the code, instead of having to replace whole interface files.
 
Back
Top Bottom