[Lua] 1st attempt @ Lua = failure (of course)

Hmm, sadly I don't seem to be getting anywhere. I guess I keep doing the same mistake over and over which I seem unable to locate/understand. It's getting frustrating.

Here's the latest iteration of my IsOverseasLocation function & test. The last few of attempts with different codes always returned "Not an Overseas Location".

Code:
function IsOverseasLocation(iX, iY, iPlayer)

	local pPlot = Map.GetPlot(iX, iY)
	if pPlot:IsWater() then
		return false
	end

	for iPlayer = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
		local pPlayer = Players[iPlayer]
		if not (pPlayer:IsAlive()) then
			return false
		end
			local capitalPlot = pPlayer:GetStartingPlot()
				
			if capitalPlot:GetArea() ~= pPlot:GetArea() then
				return true
		end
	end
end

local function InputHandler(uiMsg, wParam, lParam)
	-- Quit if not the input is not "left button pressed down"
	if uiMsg ~= MouseEvents.LButtonDown then return end
		local pPlot = Map.GetPlot(UI.GetMouseOverHex())  -- Clicked plot
		local pPlayer = Players[Game.GetActivePlayer()]
		if (IsOverseasLocation(pPlot, pPlayer)) == true then
			print ("Overseas Location")
			return
		else
			print ("Not an Overseas Location")
	end
end
ContextPtr:SetInputHandler(InputHandler);

Two things I don't understand in your previous post (which probably account for my repetitive failures):
At point epsilon you can see x (2, the value of v in A). But not u, v and w.
Why is that? 'x' doesn't seem to refer to anything above.
Also all of this answer one of you questions: what is passed from one function to the other? The function's arguments are passed. Nothing else.
But the function's arguments don't get to be defined in the function, right? Definitely, even though I reread our whole discussion and the short chapter on arguments in the Lua documentation the use of arguments as well as what they refer to remains unclear :confused:
 
Function arguments 101
Arguments are just local variables (in the called function's scope) assigned by the caller of this function.
In my example, "x" was an argument of B. Since we invoked B with u, x is now assigned in B with the value of u from A.

Here is an example:
Code:
function A(x)  [COLOR="DarkGreen"]-- x is an argument of A[/COLOR]
   print(x)
end

A("Hello world!")
local y = "This is a test."
A(y)
It will print:
Hello world!
This is a test.

In this example, x is first assigned with "Hello world" and A prints it. Then y is assigned with "This is a test.", x is assigned with that value, and A prints it (although it cannot see x).

Another example just to be sure:
Code:
function max(a, b)
   if a > b then return a end
   return b   [COLOR="DarkGreen"]-- Only executed if "a" is NOT greater than "b". That is, "a" is lesser than or equal to "b".[/COLOR]
end

print(max(1,3))  [COLOR="DarkGreen"]-- Will print 3[/COLOR]
print(max(5,6)) [COLOR="DarkGreen"]-- Will print 6[/COLOR]
print(max(4,2,6)) [COLOR="DarkGreen"]-- Will print 4, the third argument is ignored since "max" did not declare it[/COLOR]
print(max(4)) [COLOR="DarkGreen"]-- Will throw an error when a > b is tested: cannot compare number with nil. Indeed "b" is nil since it was not assigned with a value.[/COLOR]


Now look at your last piece of code: your function requests three arguments (iX, iY and iPlayer). You provide two arguments to it (pPlot and pPlayer). So iX now has the value of pPlot, iY has the value of pPlayer and iPlayer is nil. Hence an error.

Is it clear like that?



Understanding a code: step by step
You need to learn how to mentally run your code step by step. Here is one of your previous code:
Code:
function IsOverseasLocation(iPlayer, pPlot)            
        [COLOR="DarkGreen"]-- Entry point[/COLOR]
	for iPlayer=0, GameDefines.MAX_MAJOR_CIVS-1 do   [COLOR="DarkGreen"]-- Line 1[/COLOR]
	        local pPlayer = Players[iPlayer]                      [COLOR="DarkGreen"]-- Line 2[/COLOR]
		if not (pPlayer:IsAlive()) then                        [COLOR="DarkGreen"]-- Line 3[/COLOR]
			return                                               [COLOR="DarkGreen"]-- Line 4[/COLOR]
		else       
			local capitalPlot = pPlayer:GetStartingPlot() [COLOR="DarkGreen"]-- Line 5[/COLOR]
			return capitalPlot:GetArea() ~= pPlot:GetArea() [COLOR="DarkGreen"]-- Line 6[/COLOR]
                         [COLOR="DarkGreen"]-- Line 7[/COLOR]
		end
	end
end [COLOR="DarkGreen"]-- Exit point[/COLOR]
* The function enters at "entry point". iPlayer and pPlot have the values provided by the caller.
* On line 1 iPlayer is assigned with 0. Since 0 is lesser than GameDefines.MAX_MAJOR_CIVS-1 we jump at line 2
* On line 2 pPlayer is assigned with the correspond player for iPlayer
* On line 3 we check that pPlayer is dead. Let's assume it is, we jump at line 4.
* On line 4 we return nothing. Jump to exit point, end of the function.

Now let's assume the player 0 was alive. Back to line 3.
* On line 3, pPlayer is alive, jump to line 5
* On line 5 capitalPlot is assigned with pPlayer's starting plot
* On line 6 we return false if the starting plot is on the same area than the provided plot argument, true otherwise. Jump to exit point, end of the function.

As you can see the other players are never tested, whatever the sequence of events is. We never reach line 7 and we never go back to line 1. We always return during the player 0's run.



Debugging 101
Sometimes, although you do a mental step by step execution, you still cannot understand. This happens, human brains are not perfect. So we need debugging techniques to understand what happens. Nowadays programmers have powerful tools for that but not here, in civ5 modding, you're out of luck. So you need to use the most basic debugging tool: "print".

The principle is easy to understand: add print statement as often as it is necessary for you to understand what happens. Add them until you understand what works and does not work. An example:

Code:
function IsOverseasLocation(iPlayer, pPlot)
        print("entry point ", iPlayer, iPlot)
	for iPlayer=0, GameDefines.MAX_MAJOR_CIVS-1 do
	        local pPlayer = Players[iPlayer]
                print("#01 ", iPlayer)
		if not (pPlayer:IsAlive()) then
                        print("player is dead")
			return
		else       
                        print("player is alive")
			local capitalPlot = pPlayer:GetStartingPlot()
                        print(capitalPlot:GetArea(), pPlot:GetArea())
			return capitalPlot:GetArea() ~= pPlot:GetArea()
		end
	end
        print("Natural end of the function")
end
Run that and it will be easy to understand!

As you noticed, we can provide many args to "print", they will be printed with tabulations between them. You could also use concatenations (.. operator, as in "a".."b" that produces the "ab" string) but beware to nil values when concatenating (you can use tostring to overcome the problem).
 
Thanks again for your patience. I believe it's slowly sinking in my old head. :hammer2:
Quick questions before I bang it on a wall once more.

No. 1
"for iPlayer=0, GameDefines.MAX_MAJOR_CIVS-1"
I figured out that there was a missing ", 1" at some point yesterday (probably an artifact of all the editing I've been doing). Is this why it isn't looping correctly? If not, how to do the following:
As you can see the other players are never tested, whatever the sequence of events is. We never reach line 7 and we never go back to line 1. We always return during the player 0's run.
When I scan Lua files looking for that line I don't seem to find any instance of a line that would make it loop.
*edit: since I'm just looking for overseas areas by reference to each CIVs starting plot would GetActivePlayer be useful (though I kinda remember reading that active player meant human player, is that right?)

No. 2
In my last try was I using the hex picker correctly?
 
Hello!

The third parameter in the for loop is actually the step (how much you add/remove on every iteration) and it is optional. If you do not specify it, it's 1. So there was nothing wrong.

Your problem with the loop does not come from the loop. It comes from the return statements. Whatever happens the first player always reach a "return" statement that terminates the function. Return = exit right away. Do not pass go, do not collect 200$.

Fianlly, yes you do use the hex picker correctly. Well, there is an indentation problem: the first "if" has its block on the same line, so you do not need to indent after it. This is why it looks like you're missing one "end" when actually everything is fine. :)
 
A true Hollywood scenario!
The hero, bloodied and desperate, finds redemption!
Just when I was about to give up, call it quit, cry uncle, start playing MMORPGs again...
CAME THE LIGHT! :old:

My life as a modder was empty, not for lack of a call, but for want of a caller!

Alright, here's what happened:
Playing around with the more in-depth approach to debugging you suggested, I noticed that a key issue was my function's arguments were often returning nil/undefined. That's when I heard the call of the caller. On my way to the grocery shop, looking for some beers to drown my failure (and a milk carton), I had a revelation: "My iPlayer can't be defined because it is not called by the game!"

So, I sacrificed a few iX and iY on the altar of "GameEvents.UnitSetXY.Add(IsOverseasLocation)" and, lo and behold, I was heard: plopped units now know if they're on their continent of origin or abroad when they move around.

Code:
function IsOverseasLocation(iPlayer, iUnitID, pPlot)
	print("entry point ", iPlayer, iUnitID, pPlot)
	
	local pPlayer = Players[iPlayer]
		print("#01 ", iPlayer)
	local capitalPlot = pPlayer:GetStartingPlot()

	local pUnit = pPlayer:GetUnitByID(iUnitID)
	local pPlot = pUnit:GetPlot()
	
	if (capitalPlot:GetArea() ~= pPlot:GetArea()) == true then
			print ("Overseas Location", capitalPlot:GetArea(), pPlot:GetArea())
		return
		else
			print ("Not an Overseas Location")
	end
	print("Natural end of the function")
end
GameEvents.UnitSetXY.Add(IsOverseasLocation)
After days of infinite regress, one little success to keep me going.
Thank you Master Don for not giving up on me :bowdown:
 
Congratulations Laokong, I am glad for you. :)
It looks like you realized the problem with function parameters and how to use them. :goodjob:

Two little remarks: you do not need a pPlot argument (since you do not use it and redefine it instead, and because the third and fourth argument of UnitSetXY are iX and iY) so you can just remove it. And you indentation is a bit off ("return" should be on the same level as the "print" before, you second "print" does not need to be indented).
 
Hmm, yes, I see your point about pPlot. Yet, couldn't it still be useful if I want to connect other events to this function (I'm thinking about founding cities here). As long as it is in the same Lua file we can have a function modify more than one event, right? To have functions carry across files you have to add a file to the mod, am I right?

I noted that sometimes the arguments of the function are included in the event.add line and sometimes not, why is that so?
For example:
GameEvents.UnitSetXY.Add(IsOverseasLocation(iPlayer, iUnitID, pPlot))
vs.
GameEvents.UnitSetXY.Add(IsOverseasLocation)​
Thanks again
 
Hmm, yes, I see your point about pPlot. Yet, couldn't it still be useful if I want to connect other events to this function (I'm thinking about founding cities here). As long as it is in the same Lua file we can have a function modify more than one event, right? To have functions carry across files you have to add a file to the mod, am I right?
As you did use it, no, it is strictly useless since you redefine this value. Now you could achieve something like what you describe that way:
Code:
if not pPlot then pPlot = pUnit:GetPlot() end

Or, more elegantly:
Code:
pPlot = pPlot or pUnit:GetPlot()
"Or" and "and" do not return booleans in LUA, they return either the left or the right operand (the last one to be evaluated).


I noted that sometimes the arguments of the function are included in the event.add line and sometimes not, why is that so?
For example:
GameEvents.UnitSetXY.Add(IsOverseasLocation(iPlayer, iUnitID, pPlot))
vs.
GameEvents.UnitSetXY.Add(IsOverseasLocation)​
Thanks again
Nope, this is not what you saw. Look carefully, this is either:
Code:
GameEvents.UnitSetXY.Add(IsOverseasLocation)
or
Code:
GameEvents.UnitSetXY.Add(function(iPlayer, iUnitID, pPlot)
   [COLOR="DarkGreen"]-- Insert code here[/COLOR]
end)
or
Code:
GameEvents.UnitSetXY(iPlayer, iUnitID, pPlot)

The first one subscribes the event with a function defined before.
The second one subscribes the event with an anonymous function defined on the fly.
The last one triggers the event (it calls the subscribers with the provided values).
;)
 
I faced a new kind of problem while trying to run some tests with my colonist unit. I've added the 2 dependent xml files (one for the tech, the other for the unit) to my barebone mod which contained the Lua file. I know that both types of file will load correctly when loaded separately. But now that I've tried them together, I get the following message:
Code:
Runtime Error: [string "Assets\DLC\Expansion\UI\InGame\InGame.lua"]:1169: attempt to index local 'addinFile' (a nil value)
 Runtime Error: Error loading Assets\DLC\Expansion\UI\InGame\InGame.lua.
I don't see what is wrong (though I'm rather tired). Any idea?
 
I guess it is an error in the mod properties window. On the content tab, where you registered your mod as an IngameUIAddin, is the specified file correct? Are you LUA files registered into the VFS?
 
What are anonymous functions used for usually?
Well, first of all they're sometimes just cosmetic: if you need a small one-line function, you could as well define it on the fly rather than add one more entry on the file level. The typical example is a sort function:
Code:
table.sort(mytable, function(a, b) return a < b end)
Other languages encourage them further, here is an example in C# to get all the producers of keyboards below 50$
Code:
var results = items.Where(x => x.Price < 50).Select(x => producer).Distinct().OrderBy(x => x.Name);
Quite elegant, isn't it ? ;)



Now, anonymous functions also have one little trick: closures. Basically you can nest some of your local variables in the anonymous function you create and it will remember its value. Imagine that you want one of your function to return another function to be called later (on every frame, after an event, etc). But that function acts upon some data and the caller won't provide those data (you subscribe a city-related event and your function needs to check some colonists only).

You can either store the data you will need into a global (or file-scoped local) array, or into a closure. Like that:
Code:
function GiveMeAFunc(soul, style)
    local funky = DoSomethingFunky(soul)
    return function() 
        for i, v in ipairs(funky) do
             DoSomethingGroovy(v)
             print("Just funk it up! Do you like "..style.."?")
        end
    end
end

local func = GiveMeAFunc(SoulFactory.CreateOne(), "James Brown")
func()   -- Now func can be invoked without providing the data again.
 
Yes, simple as that: I hadn't included the Lua sub-folder name in Content Tab. Nothing like a good night of sleep to improve computer performance ;)
 
1- Closures
Now, anonymous functions also have one little trick: closures. Basically you can nest some of your local variables in the anonymous function you create and it will remember its value. Imagine that you want one of your function to return another function to be called later (on every frame, after an event, etc). But that function acts upon some data and the caller won't provide those data (you subscribe a city-related event and your function needs to check some colonists only).

You can either store the data you will need into a global (or file-scoped local) array, or into a closure. Like that:
Code:
function GiveMeAFunc(soul, style)
    local funky = DoSomethingFunky(soul)
    return function() 
        for i, v in ipairs(funky) do
             DoSomethingGroovy(v)
             print("Just funk it up! Do you like "..style.."?")
        end
    end
end

local func = GiveMeAFunc(SoulFactory.CreateOne(), "James Brown")
func()   -- Now func can be invoked without providing the data again.

Groovy! :dance:

I can see how that can come in handy. Though I'm not sure I could use it correctly. Just to see if I understand well.
When
Code:
local func = GiveMeAFunc(SoulFactory.CreateOne(), "James Brown")
is introduced in a later code, it will refer to the whole "function GiveMeAFunc(soul, style)" and give it exogenously defined variables "SoulFactory.CreateOne(), "James Brown", right? (I got the feelin' I'm wrong...)

2- in pairs
Since you used the following
Code:
for i, v in ipairs(funky) do
I've read bout using "in pairs" and seen it quite a few times but I'm still confused about what it really does. My impression is that in compares a series of variables to a given reference. I don't think I could use it correctly though.
If you have time, could you explain it a little? :thanx:
 
When local func = GiveMeAFunc(SoulFactory.CreateOne(), "James Brown") is introduced in a later code, it will refer to the whole "function GiveMeAFunc(soul, style)" and give it exogenously defined variables "SoulFactory.CreateOne(), "James Brown", right? (I got the feelin' I'm wrong...)
Well, GiveMeAFunc itself must be invoked at a time where SoulFactory and such are defined (otherwise it would be voodoo). However the returned function can be invoked anytime in any context, even if SoulFactory and such do not exist anymore (since the returned func keeps a copy). You could even pass this new function to another lua context, another thread or even another computer (well, civ5 does not allow that actually but it is possible in regular lua).



Regarding pairs, ipairs and next.
Actually this is quite simple. A table in LUA is made of key-values pairs. Keys may be integers (Players[0], Players[5]), strings (PlotTypes["PLOT_OCEAN"], PlotTypes.PLOT_OCEAN), or anything else (playersToTeams[pPlayer] = pTeam). Values, again, can be anything.

The "pairs" function is used to enumerate all the key-value pairs in a table. When you do use "for k, v in pairs(Players) do" the first key is stored in k and the corresponding value in v, then the second key is stored in k and the corresponding value in v, etc. It works for all tables, regardless of the keys' types or the values' types.


Now pairs return the k/v pairs in an arbitrary order (even with numeric keys) and sometimes this is not what you want. Sometimes you want to keep ordered tables and enumerate the k/v pairs in that order. Those tables are called "arrays". A table is considered an array when its keys are consecutive integers (they're called indices then). You can then enumerate the k/v pairs in an ordered fashion with ipairs, with the same syntax as pairs. Arrays also have some additional benefits: you can get the highest index of the array with the "#" operator (for i = 1, #myarray do), and you can use table.sort.

Note that the LUA reference never mentions the exact requirements for a table to be considered as an array and I had bad surprises with it, with seemingly correct arrays that were not enumerated correctly by ipairs. Afaik, the only sure ways for a table to be considered as an array are:
* At construction, use a syntax like myarray = { "the", "quick", "brown", "fox" }
* Use table.insert to add new indices, and table.remove to remove them.
Also, I guess arrays' indices must always start at 1, although the LUA tutorial pretends it's not required.


Finally, next is quite simple: next(mytable) returns k1, v1, while next(mytable, k1) returns k2, v2. So you can use it instead of "pairs". Actually "pairs" is built over "next".
 
Thanks, that clarifies the way both work a lot. I'm still not sure what would require "in pairs"/when to use it though.

Another question. CIV Wiki indicates that:
Listeners are added in much the same way as for Events and LuaEvents, either:
Code:
GameEvents.EventName.add(function(arglist)
  -- function code
end
or:
Code:
function MyFunctionName(arglist)
  -- function code
end
GameEvents.EventName.add(MyFunctionName)
Is there a reason to choose one approach or the other?
 
Hi again,
I seem to be unable to prevent colonists from settling on the continent of origin.
The following function seems to work as far as debugging goes: it prints the right statement where it should. Yet, I've tried a few ways to prevent settling new cities on the continent of origin and so far none have worked (see after the code). Any idea?

Code:
function IsOverseasColonist(iPlayer, iUnitID, iX, iY)
	print("entry point ", iPlayer, iUnitID)
	
	local pPlayer = Players[iPlayer]
	local pUnit = pPlayer:GetUnitByID(iUnitID)
	
	if pUnit == (nil or pUnit:IsDelayedDeath()) then
		print ("Unit Rising from the Dead")
		return false
	end
		
	if (pUnit:GetUnitType() == GameInfoTypes["UNIT_COLONIST"]) then
		print("Unit Type",pUnit:GetUnitType(), "coords", pUnit:GetX(), pUnit:GetY())
														
		if not (IsOverseasLocation(iPlayer, iUnitID, iX, iY)) then
			print("Colonist can't found on continent of origin")
			pUnit:CanFound() [COLOR="Red"]<-- line where I've tried to prevent settling[/COLOR]
			return false
		end
														
		if  (IsOverseasLocation(iPlayer, iUnitID, iX, iY)) then
			print("Is Overseas Colonist")
			pUnit:CanFound()
			return true
		end
	end
end
GameEvents.UnitSetXY.Add(IsOverseasColonist)

Here are a few things I've tried:
  • pUnit:CanFound(false) --> but CanFound isn't boolean, so I can understand that it doesn't work
  • pUnit:CanFound() == false --> ModBuddy won't allow it: doesn't let the function close
  • the above 2 with pUnit:IsFound() --> doesn't work
  • leaving the line empty --> of course doesn't work: unit isn't told what it can't do

Unfortunately, CIV5 Lua files don't contain any examples of pUnit:CanFound() and from the only example of pUnit:IsFound() one can understand that it is used to introduce a downstream event instead of affecting the settling action.
:confused:
 
Well, this is a part of the Civ5 API I never explored a lot. Be aware of that when reading what follows. Now I am under the impression you just cannot do that. First of all, CanFound is obviously not the tool for the job: it is just a property getter, otherwise it would be SetCanFound. The "Can~", "Is~", "Has~" functions do not modify anything.

The first thing I do in such a case is to look at GameEvents since they are made to override the standard civ5 behaviors and use your own ones instead. Unfortunately, it seems like there is no such event to control whether a player can found a city or not. So, after that I would look at "unit". Now again, unfortunately it seems like there is nothing for us: afaik, you cannot control through LUA whether an unit can achieve a given action or not. You cannot add or remove actions, etc.

Then I would check Network since some actions can only be done through it, typically to prevent multiplayer exploits, although the API design on this side is totally inconsistent (otherwise more than half of the functions would be in Network). But, again, it seems like there is nothing for you. Especially the SendDoTask has no suitable task for the job. Game could be worth a look in some rare cases but, alas, nothing here. And finally I would look at the xml data files to see how builds and actions (units folder) are coded, only to learn that everything is hardcoded and that the only available hack, adding and removings techs and features on the fly, is not really suitable. So after all of that I would curse the Firaxis developers who obviously didn't give a damn about modding and only added the tools they needed for their own scenarios and DLC.
:(

My advice: forget it. Until we get the dll source you won't be able to do that, so stick to things you can do, like provide the bonuses only if the colonist is not used on your starting continent. The good news? If you plan to modify the DLL once its code is released, then you have some months to become good with LUA before you have to switch to C++, which is much harder. ;)
 
Alright then, curse them I will! :trouble:
In the meantime, I'll learn some more programming ;)

I'm not much surprised by your answer though. The closer I was getting to it the more I felt that it would be difficult to reach my goal with the tools Firaxis provided us. Obviously CanFound and IsFound didn't look like what I needed and some testing just proved it.

There is an intriguing XML column in the Units table that is labeled <FoundAbroad>false</FoundAbroad>. Before undertaking this Lua project I played with it with no result, so I don't know what it does "live".

I came to the same conclusion as you did: provide the bonuses to overseas cities founded by colonists & let colonist found on the original continent but with no benefits. I'll probably reduce the cost of colonists and make it a replacement for settlers for those Civs which decide to research Colonialism.

While I was debugging, I couldn't help but notice that the way I set up my 2 functions meant that every unit was checked for it's overseas location every time it moved. I was wondering if, later in the game, it could become a drag on performance. Though it is very useful to have a stand alone function, I was thinking that for the time being it might be better to have it nested within the colonist function. This way it would check only this type of unit. Is it worth it?
 
Back
Top Bottom