How to restore Runtime Error reports for your Lua code

Pazyryk

Deity
Joined
Jun 13, 2008
Messages
3,584
July 12, 2013 Update: The new patch has fixed runtime errors for Lua called from GameEvents. However, I have seen at least one situation where errors crash silently without error report: when the Lua chunk was called as a registered callback from a UI button. One other reason you might want the code below is if you want errors to cause a popup in game. I've found this very useful as error lines are easy to miss when you have 100s of other lines of print.

The 60 character truncation has also been fixed, so error lines are now informative for file name.



Edit: If you want to jump straight to the Error Handler, go to post #2. This first post kind of shows how it developed and may help you understand how it works.

The Problems
Runtime Error reports used to work reliably when there was an error in your Lua code. Then they stopped (working reliably). I believe this happened with the vanilla pre-G&K patch 1.511 in June 2012, and it has always been the case for G&K. A more minor (but annoying and time-wasting) problem is that when you do get an error report, the error file path is truncated such that you can't use it. You still get a line number, but you have to guess the file. Sometimes that's easy, sometimes not (especially if you have >20 files).

I managed to track the first problem down to GameEvents here. Basically, any Lua errors downstream of a GameEvents call will crash silently without a Runtime Error report (even if it is many function calls downstream or goes through a LuaEvents call). However, errors downstream of an Events call (or a call from the Live Tuner) are reported correctly. So this is certainly a bug in the DLL somewhere. Based on whoward69's comments, it is not in the DLL we have access to. Fortunately, there is an all-Lua solution to this problem.

On the truncated file names, that sort of got fixed as a side effect of fixing problem 1 (in post #2).

Some Background
Your Lua code is always called by C++ from somewhere. To my knowledge, there are only 5 ways this can happen:
  1. GameEvents: For example, the line below is telling C++ to call MyFunction at the beginning of each turn for each player
    Code:
    GameEvents.PlayerDoTurn.Add(MyFunction)
  2. Events: Similar to above, but it's different C++ calling your function
  3. Registered UI events: these look like the code below and are also ways for C++ to call a Lua function[July 12, 2013 Edit: the button RegisterCallback is still broken. Not sure about others.]
    Code:
    Controls.DiscussButton:RegisterCallback( Mouse.eLClick, OnDiscuss );
    ContextPtr:SetShowHideHandler( OnShowHide );
    ContextPtr:SetInputHandler( InputHandler );
  4. InGameUIAddin: You probably added your mod's main Lua code this way and possibly other Lua code for driving various UI. The code in your mod that is "naked" -- i.e., outside of any function, like print("Running MyMod.lua") -- is called at some point during game init based on being added this way.
  5. the Live Tuner -- i.e., you type MyFunction() in the Live Tuner
Got that so far?

OK. When C++ calls Lua, the function called and any functions called by it (and so on...) are called a "chunk". If there is an error anywhere in that chunk, Lua stops itself and sends some information back to C++. Lua errors never (to my knowledge) cause a Crash To Desktop -- that's something only C++ can do. Of course, Lua is calling C++ any time you call something like Game.GetHolyCityForReligion(ReligionType, PlayerID) and that C++ can certainly CTD (in this example, it will do so if the religion specified isn't founded yet). But back to my point, when Lua crashes it stops and sends info back to the C++ that called it. It is up to C++ to do something with that. In situations #2-5 above, that "something" is to print an error statement. In situation #1 above, that something is nothing. So no error report. (This is clearly a bug.)

The workaround
You could wait for Firaxis to fix the GameEvents bug. I doubt that they are even aware of it and I have no idea how to communicate with them. The truncated file path issue is not actually a bug at all -- it is the default behavior of the Lua library debug.traceback method. Firaxis could fix that too (by supplying their own traceback method) but I doubt that will ever happen.

In the meantime, you can use pcall in Lua to restore error reporting (thanks to whoward69 for the tip!). Or better, you can use xpcall to restore error reporting with your own traceback function that does not truncate file paths.

The rest of this post sort of follows step-by-step the things I did when I realized I could use pcall to restore error reporting for Lua called from GameEvents. It might help if you are trying to understand what's going on. However, jump to post #2 if you just want the complete fix.

If you run this
Code:
MyFunction(arg1, arg2)
print("Hello world")
and there is a Lua error in MyFunction or anything called by it, then Lua stops and sends info back to whatever C++ called that chunk in the first place, which was probably either GameEvents or Events. "Hello world" is never printed. However, if you run this instead
Code:
value1, value2 = pcall(MyFunction, arg1, arg2)
print("Hello world")
print(value1)
print(value2)
and MyFunction (or something called by it) has an error, then Lua does not stop. Well ... it does stop what it was doing, but it returns to where it was outside of the pcall rather than all the way back to GameEvents or Events in the dll. The next statement after pcall runs, printing "Hello world", and then "false" and "[some error message]" are printed. The p in pcall is for "protected". What you are really doing is making MyFunction and anything called by it into a "chunk" and running it in "protected mode". If Lua encounters an error in that chunk then it returns control back to pcall (rather than the DLL) with two return values. The first return value from pcall is always true/false (did the function run without error?). If there was an error, then the second value is a string error message. If there was not an error, then the 2nd, 3rd, 4th,... return values correspond to the 1st, 2nd, 3rd,... values returned by MyFunction.

So you could implement your own fix like this:

Original:
Code:
function OnEveryPlayerTurn(iPlayer)
	--Your function code
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurn)
Change to:
Code:
function OnEveryPlayerTurn(iPlayer)
	--Your function code
end

function OnEveryPlayerTurnA(iPlayer)
	print(pcall(OnEveryPlayerTurn, iPlayer))
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurnA)
You can list any number of args for OnEveryPlayerTurn after the function name itself. And be careful not to do this!: pcall(OnEveryPlayerTurn(), iPlayer). pcall doesn't want the result of your function; it wants the function itself.



Or, if your mod code is organized like mine:
Code:
function OnEveryPlayerTurn(iPlayer)
	PerTurnFunctionA(iPlayer)
	PerTurnFunctionB(iPlayer)
	PerTurnFunctionC(iPlayer)
	PerTurnFunctionD(iPlayer)
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurn)

you can change it to:
Code:
function OnEveryPlayerTurn(iPlayer)
	print(pcall(PerTurnFunctionA, iPlayer))
	print(pcall(PerTurnFunctionB, iPlayer))
	print(pcall(PerTurnFunctionC, iPlayer))
	print(pcall(PerTurnFunctionD, iPlayer))
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurn)

As I said above, pcall() is "protecting" your Lua. If there is an error in PerTurnFunctionA, then it will be "contained" by the pcall; pcall will pass two results to print: false (because there was an error) and the error string. Then the next line will run normally regardless of an error in PerTurnFunctionA. In the original code block, an error in PerTurnFunctionA will crash (silently) the whole OnEveryPlayerTurn function so that B, C and D will not run.

To allow for return values, do this (I also localize the function here for better speed):
Code:
function OnEveryPlayerTurn(iPlayer)
	local PerTurnFunctionA = PerTurnFunctionA
	local success, value1, value2, value3, value4 = pcall(PerTurnFunctionA, iPlayer)
	--value1, value2, value3, value4 are the 1st through 4th return values you would have got from PerTurnFunctionA(iPlayer)
	--value1 is the error string if PerTurnFunctionA fails
	if not success then print(value1) end
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurn)

[Edit: Below is my first attempt at an Error Handler. There is a better (though harder to understand) one in post #2.]

--------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------
Slightly more advanced, but you can set up your own error handler like this:

Code:
function OnEveryPlayerTurn(iPlayer)
	local HandleError1 = HandleError1
	local PerTurnFunctionA = PerTurnFunctionA
	local PerTurnFunctionB = PerTurnFunctionB
	local PerTurnFunctionC = PerTurnFunctionC
	local PerTurnFunctionD = PerTurnFunctionD
	HandleError1(PerTurnFunctionA, iPlayer)
	HandleError1(PerTurnFunctionB, iPlayer)
	HandleError1(PerTurnFunctionC, iPlayer)
	local value = HandleError1(PerTurnFunctionD, iPlayer)
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurn)

function HandleError1(f, arg1)
	--f can have 0 or 1 arg and 0 or 1 return value
	local success, value = pcall(f, arg1)
	if success then
		return value
	else
		print(value)
		--popup code here to inform the player		
	end
end
This is a bit limiting because the function can only have 0 or 1 arg and 0 or 1 return value, and it won't give you a traceback. For a more generic error handler with traceback, move on to post #2...
 
OK, here's a full Error Handler that can take a function with any number of args or return values, gives full traceback in the Tuner and Lua.log, and gives you function name and file location that is not truncated (so you can actually read it). In the post above, I was using pcall() to "contain" an error. But pcall destroys any error information (except for the one-liner error string) before you can do anything with it. Here I'm using xpcall() which passes control to a Traceback.lua function before the error info is destroyed, so you can get and print any info you want using the debug.library (in my code here, prints stack info with function names and full file paths, but you could edit it to print any info you want from the debug.library).

Here's an example error report:
Code:
 EaMain: [string "C:\Users\Pazyryk\Documents\My Gam..."]:32: attempt to index global 'nonexistentObject' (a nil value)
 EaMain: 2: BuggeredFunction C:\Users\Pazyryk\Documents\My Games\Sid Meier's Civilization 5\MODS\Ea II The Ageless and the Divine (v 5)\Utilities\EaDebugUtils.lua: 32
 EaMain: 3: C:\Users\Pazyryk\Documents\My Games\Sid Meier's Civilization 5\MODS\Ea II The Ageless and the Divine (v 5)\EaMain\EaPrereqs.lua: 107
 EaMain: 4: Tail call
 EaMain: 5: C function
 EaMain: 6: C:\Users\Pazyryk\Documents\My Games\Sid Meier's Civilization 5\MODS\Ea II The Ageless and the Divine (v 5)\EaMain/EaMain.lua: 335
 EaMain: 7: Tail call
The first line is truncated (it is the default error string) but is still useful for telling you the nature of the error. The following lines give you the traceback with full paths and (at least for the first one) function name. The actual bug was at line 32 of the BuggeredFunction function in file EaDebugUtils.lua (level 2). BuggeredFunction was called from EaPrereqs.lua at line 107 (level 3), which was actually our entrypoint from GameEvents (it has a function name but debug.getinfo can't get it for reasons I don't understand). [For experts: I skip level 1 in the printout because it is always the Traceback.lua function itself. The function in EaPrereqs.lua was passed to the Error Hander so I believe that levels 4 - 7 all relate to the Error Handler itself (5 is the xpcall).

I said in the OP that only GameEvents error reporting was broken so you only need an error handler for that. But you can use this for Lua downstream of Events too if you want traceback info that is not truncated (or if you want to edit my Traceback.lua to get more info from the debug.library).

Usage:

Let's say you have a function with 3 args and 2 return values. You normally code:
Code:
local x, y = MyFunction(arg1, arg2, arg3)
The way you would use the error handler is like this:
Code:
local x, y = HandleError(MyFunction, arg1, arg2, arg3)
MyFunction and any functions called downstream of it are now run in "protected" mode. If there is an error then: 1) Traceback.lua will print error info (and the player will get an error popup if you enable that), 2) x and y will get nil values, and 3) the next line after the above line will run.


Here's an example for fixing a GameEvents call so you have error reporting:
Code:
local function OnEveryPlayerTurn(iPlayer)
	--function code
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurn)
Change to:
Code:
local HandleError = HandleError	--must be defined before this

local function OnEveryPlayerTurn(iPlayer)
	--function code
end

local function OnEveryPlayerTurnA(iPlayer)
	return HandleError(OnEveryPlayerTurn, iPlayer)
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurnA)
Or:
Code:
local HandleError = HandleError	--must be defined before this

local function OnEveryPlayerTurn(iPlayer)
	--function code
end
GameEvents.PlayerDoTurn.Add(function(iPlayer) return HandleError(OnEveryPlayerTurn, iPlayer) end)
I localized functions there for better speed (using unlocalized functions as args for other functions is really slow). Remember that HandleError needs the function args listed explicitly.

There is another error handler in the code below called HandleError41. It works exactly the same but can only take 0-4 args and can only give 0 or 1 return value. It can't do anything that HandleError can't, but it's a bit faster. I use it for the "Can" GameEvents since those run many times per turn and always have one return value. You can write your own HandleError10, HandleError21, etc., if you are obsessed with speed.

Code:

This will print to the Live Tuner and has optional code to give the player a Popup informing them of a program error (you can add that part or ignore it). It will run with or without the debug library (i.e., EnableLuaDebugLibrary = 1 or 0 in config.ini) but you will only get the first line above if it is set to 0 (so no traceback and truncated location info only).
Code:
local g_errorStr = "none"
local function Traceback(str)
	print(str)
	if not debug then return str end
	if str == g_errorStr then		--debug.getinfo is slow, so don't run 1000 times for the same error
		return str 
	end
	g_errorStr = str
	print("stack traceback:")
	str = str .. "[NEWLINE]stack traceback:"	--str is only needed after this point for the popup
	local level = 2		--level 1 is this function
	while true do
		local info = debug.getinfo(level)
		if not info then break end
		local errorLine = ""
		if info.what == "C" then   -- is a C function?
			errorLine = string.format("%d: %s", level, "C function")
		elseif info.source == "=(tail call)" then
			errorLine = string.format("%d: %s", level, "Tail call")
		elseif not info.name or info.name == "" then
			errorLine = string.format("%d: %s: %d", level, (info.source or "nil"), (info.currentline or "-1"))
		else 
			errorLine = string.format("%d: %s %s: %d", level, (info.name or "nil"), (info.source or "nil"), (info.currentline or "-1"))
		end
		print(errorLine)
		str = str .. "[NEWLINE]" .. errorLine
		level = level + 1
	end
	return str
end

function HandleError(f, ...)
	--f can have any number of args and return values
	local g = function() return f(unpack(arg)) end --need wrapper since xpcall won't take function args
	local result = {xpcall(g, Traceback)}
	if result[1] then
		return unpack(result, 2)
	end
	--DoErrorPopup(result[2])
end

function HandleError41(f, arg1, arg2, arg3, arg4)
	--f can have up to 4 args and 0 or 1 return value
	local g = function() return f(arg1, arg2, arg3, arg4) end --need wrapper since xpcall won't take function args
	local success, value = xpcall(g, Traceback)
	if success then
		return value
	end
	--DoErrorPopup(value)	
end

Optional player popup:

This gives the full error text in an in-game popup. If the player has EnableLuaDebugLibrary = 0, then it makes a nice suggestion that they might want to consider changing that to 1 for better error reporting. You need to enable the DoErrorPopup lines in the HandleError functions above to make this work. You might have to re-write a bit to integrate with other UI in your mod. In my case below, this is the only UI associated with the mod's main Lua state so I just show and hide the whole Context.
Code:
[B]--in MyMod.lua[/B]
local g_bUpdateErrorPopup = true
function DoErrorPopup(str)
	if g_bUpdateErrorPopup then
		local text = "There have been one or more program errors:[NEWLINE][NEWLINE]" .. (str or "")
			.. "[NEWLINE][NEWLINE](If you have set LoggingEnabled = 1 in your config.ini, then this messege has been printed in the Lua.log)"
		if not debug then
			text = text .. "[NEWLINE][NEWLINE](Please also set EnableLuaDebugLibrary = 1 for more informative error reporting; it is currently set to 0)"
		end
		text = text ..  "[NEWLINE][NEWLINE]Please relax, post your Lua.log on the mod thread, and go have a beer..."
		Controls.DescriptionLabel:SetText(text)
		ContextPtr:SetHide(false)
		g_bUpdateErrorPopup = false
	end
end

function CloseErrorPopup()
	g_errorStr = "none"
	g_bUpdateErrorPopup = true
	ContextPtr:SetHide(true)
end
Controls.CloseErrorButton:RegisterCallback(Mouse.eLClick, CloseErrorPopup)

ContextPtr:SetHide(true)


[B]--in MyMod.xml[/B]
<?xml version="1.0" encoding="utf-8"?>
<!-- This is the Error Popup controlled by ErrorHandlers -->
<Context Font="TwCenMT14" FontStyle="Base" Color="Beige" Color1="Black" >
	<Box Size="1000,800" Anchor="C,C" Color="Black.200" AnchorSide="O.I" Offset="0,0" ConsumeMouse="1">
		<Label ID="DescriptionLabel" Anchor="L,T" Offset="10,10" WrapWidth="992" LeadingOffset="-4"  Font="TwCenMT18" FontStyle="Shadow" ColorSet="Beige_Black_Alpha" String="Program Error!" />
		<GridButton Anchor="R,B" AnchorSide="i,i" Size="100,32" Offset="0, 0" String="TXT_KEY_CLOSE" ID="CloseErrorButton" Style="BaseButton" ConsumeMouse="1" />

	</Box>
</Context>
 
There are some grumblings on the internets about pcall/xpcall not playing nicely with coroutines. E.g., http://lua-users.org/lists/lua-l/2004-10/msg00130.html

Unfortunately, whoward69's plot iterator function uses coroutines. I'm not sure if there is a problem or not -- they give this as an example of what won't work:

Code:
	function a()
	  coroutine.yield()
	end

	function b()
	  assert( pcall(a) )
	end

	co = coroutine.create(b)

	assert( coroutine.resume(co) )

Perhaps everything is fine if the coroutine stuff is all downstream of the pcall/xpcall. If not, there might be a possible fix (but I don't understand coroutines enough to know what it would be). Maybe whoward69 can comment.
 
The answer to that, like most things, is it depends. In this case where the error occurs.

If the error is inside the co-routine, pcall() won't catch it. If the error is in the code that uses the co-routine, pcall() will catch it

Code:
-- Very simplified version of the coroutines used by the Plot Iterators
function IntIterator(max, failInner, failOuter)
  local next = coroutine.create(function ()
    for i=1, max, 1 do
	  if (failInner and i == failInner) then
	    -- Access something that doesn't exist
		local x = err + i
	  end

	  coroutine.yield(i)
    end

    return nil
  end)

  return function ()
    local _, ret = coroutine.resume(next)

	  if (failOuter and ret == failOuter) then
	    -- Access something that doesn't exist
		local x = err + ret
	  end

	  return ret
  end
end

-- Worker function using the simplified coroutine iterator
function ShowNumbers(msg, max, failInner, failOuter, failLoop)
  print(msg)

  for i in IntIterator(max, failInner, failOuter) do
	if (failLoop and i == failLoop) then
	  -- Access something that doesn't exist
	  local x = err + i
	end

	print(i)
  end

  print("===")
  print()
end

-- Now call the worker function
-- These both work as expected
ShowNumbers("normal", 4)
pcall(ShowNumbers, "pcall", 7)

-- These will both fail with a "cannot resume dead coroutine" message
-- ShowNumbers("normal - inner fail", 4, 3, nil)
-- pcall(ShowNumbers, "pcall - inner fail", 7, 4, nil)

-- This will get as far as 1, 2 and the fail with a "attempt to perform arithmetic on global 'err' (a nil value)" message, the final message is not printed
-- ShowNumbers("normal - outer fail", 4, nil, 3)

-- This will get as far as 1, 2, 3 and then fail, and the final message is printed
-- pcall(ShowNumbers, "pcall - outer fail", 7, nil, 4)

-- This will get as far as 1, 2 and the fail with a "attempt to perform arithmetic on global 'err' (a nil value)" message, the final message is not printed
-- ShowNumbers("normal - loop fail", 4, nil, nil, 3)

-- This will get as far as 1, 2, 3 and then fail, and the final message is printed
pcall(ShowNumbers, "pcall - loop fail", 7, nil, nil, 4)

-- If all goes well (or we trap any errors) we should get here
print("That's all folks!")
 
Based on some additional explanation from whoward69, I believe that there is no problem using his plot iterator function in conjunction with my code above. If you edit his coroutines though and introduce an error, setjmp.h will eat your motherboard and spit it out as confetti!
 
A minor change to the iterator code means that we can end up with very little "unprotected" code - so very little to check, double check, and even if we mess up, to go wrong

Only the lines of code in red below are unprotected, and that's just a function that contains a loop - not a huge amount of room to foobar!

So that's a pcall() inside a coroutine inside a pcall()!

Code:
-- Very simplified version of the coroutines used by the Plot Iterators
function IntIterator(max, failInner, failOuter)
  local function inner(i)
    if (failInner and i == failInner) then
      -- Access something that doesn't exist
      local x = err + i
    end
  end

[COLOR="Red"]  local next = coroutine.create(function ()
    for i=1, max, 1 do
      if (pcall(inner, i)) then
        -- All was OK, so yield
        coroutine.yield(i)
      else
        -- inner() caused an error, so terminate the loop (assume something else reported the error)
        return nil
      end
    end

    return nil
  end)[/COLOR]

  return function ()
    local _, ret = coroutine.resume(next)

    if (failOuter and ret == failOuter) then
      -- Access something that doesn't exist
      local x = err + ret
    end

    return ret
  end
end

-- Worker function using the simplified coroutine iterator
function ShowNumbers(msg, max, failInner, failOuter, failLoop)
  print(msg)

  for i in IntIterator(max, failInner, failOuter) do
    if (failLoop and i == failLoop) then
      -- Access something that doesn't exist
      local x = err + i
    end

    print(i)
  end

  print("===")
  print()
end

-- Now call the worker function
-- These both work as expected
ShowNumbers("normal", 4)
pcall(ShowNumbers, "pcall", 7)

[COLOR="Magenta"][B]-- These will now fail gracefully[/B][/COLOR]
ShowNumbers("normal - inner fail", 4, 3, nil)
-- pcall(ShowNumbers, "pcall - inner fail", 7, 4, nil)

-- This will get as far as 1, 2 and the fail with a "attempt to perform arithmetic on global 'err' (a nil value)" message, the final message is not printed
-- ShowNumbers("normal - outer fail", 4, nil, 3)

-- This will get as far as 1, 2, 3 and then fail, and the final message is printed
-- pcall(ShowNumbers, "pcall - outer fail", 7, nil, 4)

-- This will get as far as 1, 2 and the fail with a "attempt to perform arithmetic on global 'err' (a nil value)" message, the final message is not printed
-- ShowNumbers("normal - loop fail", 4, nil, nil, 3)

-- This will get as far as 1, 2, 3 and then fail, and the final message is printed
-- pcall(ShowNumbers, "pcall - loop fail", 7, nil, nil, 4)

-- If all goes well (or we trap any errors) we should get here
print("That's all folks!")


EDIT: The Lua 5.1 source code dealing with co-routines, pcall and yield can be found here http://www.lua.org/source/5.1/lbaselib.c.html#co_funcs and here http://www.lua.org/source/5.1/ldo.c.html
(Just adding that in case I ever need to find them again!)
 
It seems like any errors in map generation code fail to report a message, sending just a blank line to lua.log. I've encountered this problem in AssignStartingPlots and NaturalWondersCustomMethods. There might be a broader problem than just these map creation files. These are the only ones I currently include in the VFS in my project. I haven't gotten to my other overridden files yet. I suspect it's an issue with VFS-included lua files.

Code:
[132058.855] Map Script: Placing Natural Wonders.
[132058.871] 
[132059.042] LoadScreen: Init ModTools.lua

One interesting thing I noticed is this blank-line problem only happens for games started from the Mods menu. I have a map script. If an error happens when I started the map through the mods menu, it sends a blank line to the log. If I go back to the singleplayer menu to start the map there, I get a proper error report.


Edit: I'm starting to think this is a problem for mods which override core lua files by including modified versions in the VFS. I knew I had a bug in my customized NaturalWondersCustomMethods, but it gave the blank message above. I copied my code into the core Expansion2 version of the file I'm overriding, started through the singleplayer menu, and got a full error report.

Code:
[132559.103] Map Script: Placing Natural Wonders.
[132559.119] Runtime Error: Assets\DLC\Expansion2\Gameplay\Lua\NaturalWondersCustomMethods.lua:297: attempt to call field 'Constrain' (a nil value)
stack traceback:
	Assets\DLC\Expansion2\Gameplay\Lua\NaturalWondersCustomMethods.lua:297: in function 'Plot_GetPlotsInCircle'
	Assets\DLC\Expansion2\Gameplay\Lua\NaturalWondersCustomMethods.lua:90: in function 'NWCustomEligibility'
	Assets\DLC\Expansion2\Gameplay\Lua\AssignStartingPlots.lua:5254: in function 'CanBeThisNaturalWonderType'
	Assets\DLC\Expansion2\Gameplay\Lua\AssignStartingPlots.lua:5862: in function 'GenerateNaturalWondersCandidatePlotLists'
	Assets\DLC\Expansion2\Gameplay\Lua\AssignStartingPlots.lua:6032: in function 'PlaceNaturalWonders'
	Assets\Maps\Continents.lua:318: in function 'StartPlotSystem'
	Assets\DLC\Expansion2\Gameplay\Lua\MapGenerator.lua:815: in function <Assets\DLC\Expansion2\Gameplay\Lua\MapGenerator.lua:779>
	=[C]: ?

A possible workaround is to make a backup of the files you want to override, copy code into the core files, then debug and test your modifications through the singleplayer menu. This can be difficult if the overridden files rely on other mod files, but it's a starting point.
 
@Thalassicus, you could also find the "entry point" for the Lua chunk and add my Error Handler above.

I'm quite sure from my experimentation (and your results don't contradict it) that the file and method of file addition don't matter at all. The only thing that matters is what C++ originally called the Lua chuck that contains the error.

Try calling your bugged function from Live Tuner, GameEvents, or Events. You will see the error. But try hooking it to a registered callback button for UI. You will see only the blank line. I wouldn't be surprised if map scripts were being called (ultimately) from a registered callback button in the game setup menu but only for mods. Non-modded games call the map script from some other C++ source (I'm guessing after the UI sends Network command back to C++).

It is up to the calling source (the C++) to do something (or not) when an error occurs in a Lua chunk. The "chunk" (that's the technical term) includes all Lua downstream of the calling C++, no matter where or how indirect (even if in different states via LuaEvents).

So all you need to do is find the "entry point" for the Lua chunk that includes the map script but only for modded games. I'll bet it goes back to something like this in the UI code:
Code:
Controls.StartButton:RegisterCallback( Mouse.eLClick, OnStart )
OnStart is what I mean by "entry point". It is called by C++. That C++ is bugged in such a way that it won't report errors as it is supposed to (only gives the blank line). We can't fix the bug because we don't have access to that particular C++. But fortunately we can isolate Lua errors within Lua (with xpcall) and make our own traceback. Using my error handler above it would look like this:
Code:
Controls.StartButton:RegisterCallback( Mouse.eLClick, function() return HandleError(OnStart) end)
 
Back
Top Bottom