Are we able to reference ('include') lua files yet?

ww2commander

Emperor
Joined
Aug 23, 2003
Messages
1,243
Location
Australia
With the release of the SDK and Summer Patch, can we reference other lua files via an 'include' statement in the main script file that Civ VI uses for any lua code.

This seems like a necessary aspect of any large mod and I am not sure if there is another way to do this right now?
 
With the release of the SDK and Summer Patch, can we reference other lua files via an 'include' statement in the main script file that Civ VI uses for any lua code.

This seems like a necessary aspect of any large mod and I am not sure if there is another way to do this right now?
I think I've seen a post about include still not working...

What I'm using now is a bit complex but working, we've discussed it in another thread but I don't remember which one, so here is an example based on my current work.

I'm using the ExposedMembers table which is shared by all contexts to share my own functions

See this conversion of whoward's RouteConnections:
https://github.com/Gedemon/Civ6-GCO/blob/master/Scripts/RouteConnections.lua

at the end of the file after all the file functions are defined, I do this:
Code:
----------------------------------------------
-- Initialize functions for other contexts
----------------------------------------------

ExposedMembers.RouteConnections_Initialized = false

function Initialize()
    if not ExposedMembers.GCO then ExposedMembers.GCO = {} end
       
    -- Route Connections
    ExposedMembers.GCO.IsCityConnected                 = isCityConnected
    ExposedMembers.GCO.IsPlotConnected                 = isPlotConnected
    ExposedMembers.GCO.GetRouteLength                 = getRouteLength
    ExposedMembers.RouteConnections_Initialized        = true
end
Initialize()

If I had used
Code:
include("RouteConnections.lua")
as in civ5, I could call isPlotConnected(parameters) after the include

now I can use directly ExposedMembers.GCO.IsPlotConnected(parameters) in any context, but for simplicity I use something like that in a file

Code:
local GCO = {}
function InitializeUtilityFunctions()     -- Get functions from other contexts
    if ExposedMembers.RouteConnections_Initialized then 
        GCO = ExposedMembers.GCO        -- contains functions from other contexts
        Events.GameCoreEventPublishComplete.Remove( InitializeUtilityFunctions )
        print ("Exposed Functions from other contexts initialized...")
    end
end
Events.GameCoreEventPublishComplete.Add( InitializeUtilityFunctions )

and call GCO.IsPlotConnected(parameters) in that file.

See beginning and end of https://github.com/Gedemon/Civ6-GCO/blob/master/Scripts/ModUtils.lua for more details on how I initialize everything, because of course you have to wait before you start calling those functions (especially if you're using UI context function in script context as the file you'll get the function from is loaded a bit of time after the script)
 
So _G.IncludeFileList in Main State contains the games 'includes', but it seems we can not add an entry to this table via our own script....is this a correct assumption?

I did a for-next on this table and all the map and UI lua files printed.
 
Maybe there is a way to add our files in that list with modbuddy (or a tag in modinfo), I've not tried it yet.
 
So I'm patiently 'giving it a chance', but it seems that this is a huge oversight and obstacle for coding in general. :wallbash:

It begs the question....why even take this approach given it worked fine in civ5 modding.

Hopefully some break-thru is made or Firaxis drops a hint as to how modders can work across multiple scripts. Right now, the tutorial is the only scenario I have come across that uses more than 1 lua file, but it is loaded as a part of the overall init process thus its files end up being included in the aforementioned _G.IncludeFileList.
 
It may be related to this part of the developer comment posted on discord:
The gameplay DLL is running on a separate thread and has it's own set of lua exposures. The UI scripts act on cached data that may not be 100% in sync with the state of the gameplay dll (for example if it's playing back combat) Because of this the UI-side lua scripts have some different exposures than the gameplay side.

Now my solution is (currently) working, I have already 10 scripts working together in the mod I've linked.
 
In theory, should it not be a simple case of creating the ExposedMembers in the Initialize() function and that's all that is necessary.

Each new or loaded game runs this function which in turn creates the references. Tracking initialization and whether table entries have been created seems unnecessary as long as everything is being contained in the one spot.

I could be wrong, so just throwing the observation out there...

Example within my Utils_Units.lua file:
Code:
-- ###############################################################################
-- INITIALIZE FUNCTION
-- ###############################################################################

function Initialize()
    print("Initializing Utils_Units")
    ExposedMembers.Utils_Units = {}
    ExposedMembers.Utils_Units.CreateUnit        = CreateUnit
    ExposedMembers.Utils_Units.DeleteUnit        = DeleteUnit
    ExposedMembers.Utils_Units.GetUnitKey        = GetUnitKey
    ExposedMembers.Utils_Units.GetUnitFromKey    = GetUnitFromKey

end
Initialize()

Then I simply call it from the primary script via ExposedMembers.Utils_Unites.CreateUnit(...)
 
I just tried every possible method I could think of so far as modinfo settings and the new modbuddy settings. Still a no-go with including a file not in the previously-established list.
 
In theory, should it not be a simple case of creating the ExposedMembers in the Initialize() function and that's all that is necessary.

Each new or loaded game runs this function which in turn creates the references. Tracking initialization and whether table entries have been created seems unnecessary as long as everything is being contained in the one spot.

I could be wrong, so just throwing the observation out there...

Example within my Utils_Units.lua file:
Code:
-- ###############################################################################
-- INITIALIZE FUNCTION
-- ###############################################################################

function Initialize()
    print("Initializing Utils_Units")
    ExposedMembers.Utils_Units = {}
    ExposedMembers.Utils_Units.CreateUnit        = CreateUnit
    ExposedMembers.Utils_Units.DeleteUnit        = DeleteUnit
    ExposedMembers.Utils_Units.GetUnitKey        = GetUnitKey
    ExposedMembers.Utils_Units.GetUnitFromKey    = GetUnitFromKey

end
Initialize()

Then I simply call it from the primary script via ExposedMembers.Utils_Unites.CreateUnit(...)
As long as you know you're not going to call ExposedMembers.Utils_Unites.CreateUnit before Utils_Units.lua is loaded, yes.

In my case I want to be sure everything is loaded ASAP, for example when loading a saved game I need to load my unit table in UnitScript.lua before the units are placed on the map by the game's engine and after the SaveLoad.lua file (containing the functions for, well, loading and saving my tables) is initialized, and AFAIK the Events like LoadGameViewStateDone or AppInitComplete fire too late or too early for this use.
 
Well that's good to know and puts some of my concerns at ease. Yes I agree that strict discipline is required when coding going forward to prevent these circumstances arising.

I have decided to reference via LuaEvents and only if needed use ExposedMembers. As I have yet to venture into UI territory, I am sure there will be a lot of headaches with this approach...but the journey is sometime more fun than the destination.
 
See beginning and end of https://github.com/Gedemon/Civ6-GCO/blob/master/Scripts/ModUtils.lua for more details on how I initialize everything, because of course you have to wait before you start calling those functions (especially if you're using UI context function in script context as the file you'll get the function from is loaded a bit of time after the script)
I've looked through your files (impressive, btw!) and I don't see what you mean by "you have to wait before you start calling those functions". In each .lua you've got Initialize() (+ShareFunctions()) and you simply assign the functions you want exposed to ExposedMembers.GCO.<function>. And you set flag that it was initialized. Then in other scripts you use them through local GCO (=ExposedMembers.GCO).
I probably miss something here because e.g. I don't know what's the purpose of LuaEvents.InitializeGCO. You register events here and later call it (?)

Separate question. Why do you have empty .xml registered as UserInterface components? Also, some are in InGame context and one (?) in its own.
 
There are things that some scripts can't do before some others are loaded.

For example I can't load the units table in UnitScript.lua before the save/load function from SaveLoad.lua are available.

And as I use a local table (because I'm lazy and don't want to type ExposedMembers.GCO.<function> everytime), I need to wait for everything to be added in ExposedMembers before setting the local table.

When every file is loaded, ModUtils.lua call LuaEvents.InitializeGCO() which in turn call all functions added to it with LuaEvents.InitializeGCO.Add() in the other files.

SaveLoad.xml/lua and ContextFunctions.xml/lua are scripts loaded in the InGame context so they can pass functions from that context to the other scripts that don't have access to them in the GamePlay context.

ModInGame.xml/lua will be used for UI elements, that's the normal use of InGame context.
 
Ok, I understand the use of LuaEvents.InitializeGCO. Thanks!
I understand also, same as @ww2commander said, that as long as I'm sure that a function will be properly exposed before usage, I don't need any workarounds with LuaEvents, etc.

SaveLoad.xml/lua and ContextFunctions.xml/lua are scripts loaded in the InGame context so they can pass functions from that context to the other scripts that don't have access to them in the GamePlay context.
I remember a discussion about different sets of available functions in GamePlay vs. UI context. So, it could be possible then using this mechanism to access UI context data in GamePlay. I could either create wrappers or expose directly a desired function. I think I will test this tomorrow...
 
Last edited:
I remember a discussion about different sets of available functions in GamePlay vs. UI context. So, it could be possible then using this mechanism to access UIxontext data in GamePlay. I could either create wrappers or expose directly a desired function. I think I will test this tomorrow...
Yes, just remember the developer comment about UI scripts acting on cached data (and so may not be in sync with the state of things in the script context)
 
For example I can't load the units table in UnitScript.lua before the save/load function from SaveLoad.lua are available.
And as I use a local table (because I'm lazy and don't want to type ExposedMembers.GCO.<function> everytime), I need to wait for everything to be added in ExposedMembers before setting the local table.
I am also lazy, so I was looking for a way to avoid this complication with an extra event call.

Basically in each Lua script I put something like that:

Code:
local RND = ExposedMembers.RND;
-- entire script code
function Initialize()
  if not ExposedMembers.RND then ExposedMembers.RND = {} end;
  RND = ExposedMembers.RND;
  -- add functions here to expose (optional)
  -- some other code that doesn't use exposed functions!
end
Initialize();

Basically, whichever script is loaded first, creates ExposedMembers.RND table for holding exposed functions. Then in each script local RND is either initialized at the begining (if some other script earlier created an entry in ExposedMembers), or within Initialize() function. I assume, that (by design) (1) no code requiring exposed functions is executed within Initialize(). Also, I assume (2) that the game's engine doesn't start working (e.g. throwing events) until all Lua scripts are loaded.

So, can anybody tell if there's any scenario when this approach wouldn't work?

@Gedemon Also, regarding UnitScript.lua. I assume that loading units happens in one of events related to game start/loading. So, if assumtpion (2) holds, you will always have SaveLoad functions properly exposed.
 
Unless I've missed something, if you have scriptA loading before scriptB, Initialize() in scriptA is called before scriptB is loaded (and before its function are exposed)
 
Yes, it is. It doesn't matter though. I will start using RND object after all scripts are loaded and all functions will be exposed and registered within RND. I only must not use them Initialize() itself.
 
Last edited:
Back
Top Bottom