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

Laokong

Chieftain
Joined
Jul 31, 2009
Messages
36
Alright, so I'm a total noob at programming (Lua or anything else), but I have this idea of how I'd like to improve one aspect of the game (playing on Terra, see http://forums.civfanatics.com/showthread.php?t=470924).

My first step in that direction was creating a new tech (Colonialism) which allows the training of a new unit, the Colonist. All of this was done in xml without much problems.

Now, I need to use Lua to have the Colonist do the following:
1- settle new cities only on new continents (xml <FoundAbroad> doesn't appear to discriminate by continents);
2- upon settling a colony on a new continent: (a) set city pop to 3, (b) add a few free buildings;
3- ensure that regular Settlers settling on a new continent don't get bonuses.

I was hoping to use the "GetContinentArtType()" function (as per whoward69's UI - Overlay Continents) as a proxy to discriminate between the continent of origin and the location of the colony (on Terra maps all CIV start on Europe).

I've tried 2 approaches to deal with the Lua coding:

1- A modular approach
Spoiler :

--Is founding unit settler or colonist?
GameEvents.PlayerCityFounded.Add(function isCityFoundedByColonist(iPlayer, iUnit))
local pPlayer = Players[iPlayer]
local pUnit = pPlayer:GetUnitByID(iUnit)

if pUnit = unit:canFound() == true then
if (pUnit == nil or pUnit:IsDelayedDeath()) then

if (pUnit:GetUnitType() == GameInfoTypes["UNIT_COLONIST"]) then
CityFoundedByColonist()
if (pUnit ~= nil and pUnit:IsFound()) then
pUnit:Kill();
print("City is Founded By Colonist:",plotID, "coords",pPlot:GetX(), pPlot:GetY());
end
end
end
Events.SerialEventCityCreated.Add(CityFoundedByColonist)

-- Is new city founded overseas? (modeled on UI-Overlay Continent v.5)
-- From MapGenerator.lua, 0=Ocean, 1=America, 2=Asia, 3=Africa, 4=Europe
GameEvents.PlayerCityFounded.Add(function IsOverseasCity(iHexX, iHexY)
for iPlot = 0, Map.GetNumPlots()-1, 1 do
local pPlot = Map.GetPlotByIndex(iPlot)
local iCity = pCity:GetPlotCity()

if (not pPlot:IsWater()) then
local ColonyLocation = pCity[pPlot:GetContinentArtType()]
local startPlot = pPlayer:GetStartingPlot()
local startPlotContinent = startPlot:GetContinentArtType()

if (ColonyLocation ~= nil and ~= startPlotContinent()) then
OverseasCity(iHexX, iHexY)
print("City is Overseas:",plotID, "coords",pPlot:GetX(), pPlot:GetY(), Map:GetPlotByIndex());
end
end
end
end
Events.SerialEventCityCreated.Add(OverseasCity)

--From Medieval Scenario/TurnsRemaining.lua
GameEvents.PlayerCityFounded.Add(function NewColony(iPlayer, iCityX, iCityY))
local pPlayer = Players[iPlayer];
local iCity = pCity:GetPlotCity()
local NewColony = pCity()
if (pPlayer ~= nil) then
--Is city an overseas colony?
if (pCity:CityFoundedByColonist() and pCity:OverseasCity()) then
if not pPlayer:IsMinorCiv() then
NewColony:SetPopulation(3, true);

local iBuildingID;
iBuildingID = GameInfoTypes["BUILDING_MONUMENT"]
NewColony:SetNumRealBuilding(iBuildingID, 1)
iBuildingID = GameInfoTypes["BUILDING_SHRINE"]
NewColony:SetNumRealBuilding(iBuildingID, 1)
iBuildingID = GameInfoTypes["BUILDING_GRANARY"]
NewColony:SetNumRealBuilding(iBuildingID, 1)
iBuildingID = GameInfoTypes["BUILDING_LIBRARY"]
NewColony:SetNumRealBuilding(iBuildingID, 1)
--Is city coastal or inland?
if pCity:IsCoastal() == true then
iBuildingID = GameInfoTypes["BUILDING_LIGHTHOUSE"]
NewColony:SetNumRealBuilding(iBuildingID, 1)
end
end
end
end
end
Events.SerialEventCityCreated.Add(NewColony)


2- An integrated approach
Spoiler :

GameEvents.PlayerCityFounded.Add(function OverseasCityFoundedByColonist(iPlayer, iUnit))
local pPlayer = Players[iPlayer]
for iPlot = 0, Map.GetNumPlots()-1, 1 do
local pPlot = Map.GetPlotByIndex(iPlot)
local pOrigin = pCity:IsCapital()
for pOrigin = 0, Map.GetNumPlots()-1, 1 do
local pOrigin = Map.GetPlotByIndex(iPlot)
if pPlot:GetContinentArtType() ~= pOrigin:GetContinentArtType() then
print("Overseas Plot:",plotID, "coords", pPlot:GetX(), pPlot:GetY(), Map:GetPlotByIndex());
--Is founding unit a colonist?
local pUnit = pPlayer:GetUnitByID(iUnit)
if (pUnit:GetUnitType() == GameInfoTypes["UNIT_COLONIST"]) and (pUnit:canFound(<Plot> pPlot, boolean bTestVisible = false)) then
City:SetPopulation(3, true)
local iBuildingID
iBuildingID = GameInfoTypes["BUILDING_MONUMENT"]
City:SetNumRealBuilding(iBuildingID, 1)
iBuildingID = GameInfoTypes["BUILDING_SHRINE"]
City:SetNumRealBuilding(iBuildingID, 1)
iBuildingID = GameInfoTypes["BUILDING_GRANARY"]
City:SetNumRealBuilding(iBuildingID, 1)
iBuildingID = GameInfoTypes["BUILDING_LIBRARY"]
City:SetNumRealBuilding(iBuildingID, 1)
--Is city coastal or inland?
if pCity:IsCoastal() == true then
iBuildingID = GameInfoTypes["BUILDING_LIGHTHOUSE"]
City:SetNumRealBuilding(iBuildingID, 1)
end
end
end
end
end
end
end
end
Events.SerialEventCityCreated.Add(OverseasCityFoundedByColonist)


Both approaches have failed.

I'd appreciate if you could help with those codes.

Now, I'm fully aware that these codes probably look like feeble pedestrian attempts at coding in Lua... After spending 2 weeks reading Lua tutorials/references, CIVFans Lua threads and countless Lua files, I feel like I have a grasp on the Lua vocab and functions but that I still don't understand the syntax/grammar of connecting Lua to CIV5.

So, in addition to helping me fix the above codes, I'd appreciate if someone could either point me in the direction of some references or enligthen me regarding the following structures:

1) "for iPlot = 0, Map.GetNumPlots()-1, 1 do": Why does iPlot = 0 and why add -1.

2) when do you need to specify the content of "()" and when can you leave it empty? As in NewColony(iPlayer, iCityX, iCityY).

3) What should go within the "()"?

4) When should a new Lua code be ended by a GameEvent or SerialEvent and when can it be ended without mention of such an event.

5) When and what should be defined as local and when is it unnecessary to define elements of the function.

Thanks a lot in advance! :)
 
Hello. :)
First of all, if you don't know programming it will be hard. But it's definitely possible and it can be very entertaining, gratifying and useful to learn programming. I wish you the best things for your attempt.

Advices:
* Use indentation (tabs at the beginning of lines), it will make your code so much clearer and your life so much easier.
* Use Firetuner: any error will be printed in it in live, this allows interactive debugging and this is the best friend of any programmer, especially for a beginner.
* The modular approach is the good one: small (<10 lines) and specialized functions are almost always the best way to go.
* Start small: first write five lines of code that will only print something in Firetuner (through the "print" function) anytime an event occurs. Check that it works. Then modify this function to do something useful. Then add more features. Etc... Step by step, this is is a rule to follow anytime a developer approaches a new technology or environment.


Some of your mistakes:
* Your functions are incorrectly defined, you need to use the word "function". Ex:
Code:
function CityFoundedByColonist()
    print("it works!")
end
Events.SerialEventCityCreated.Add(CityFoundedByColonist)
* "if pUnit = unit:canFound() == true then" and some other lines do not make sense or violate syntaxe.
* You define functions within conditional branches, this is not what you want.
* Actually, your whole code does not make sense, I suggest you just scratch it all and restart, step by step. Start with the little piece of code I just provided and build up over that. Slowly, safely.
* At the time SerialEventCityCreated will be invoked, I guess the colonist will no longer exist, so you need to detect it before it founds the city. I think there is an event triggered whenever a unit moves (SerialSetXY or something like that), you could use it.
* Sometimes you use a variable with a given name, then you try to use it a couple of lines later with a different name. Again: step by step.


What you should do:
* Continents can be identified through their area ID. One area par continent and sea, and one for the ocean. Use Map.Areas (or GetAreas I don't remember), plot:GetArea() and plot:Area(), etc.
* I don't know how to make the AI move the colonist. Ordering the unit to move is a piece of cake but can we prevent the AI to change the unit's orders? Maybe we must update its orders on every turn. I think this feature can be achieved but it's only a guess and it could be tricky.
* Updating the founded city is definitely possible and you correctly guessed what you need to do in substance, minus the problem that forces you to store the colonists' position in advance. You also almost correctly implemented some parts.


Your questions:
1) In most programming languages like C/C++ (that was used to develop civ5), arrays start at index 0. This is because an array is just a memory address and you get the i-th element by adding "i" times the size of one element to this starting address. If arrays were starting at 1, the CPU would always have to add 1 anytime an array element is accessed, and this would mean one unnecessary instruction. Now in LUA arrays start at 1 because this language was made for the ease of use by beginners and does not focus on performances (besides the tables in LUA are much more complex than regular arrays). But since the API behind civ5 was developed in C/C++ the devs kept the C/C++ convention when exposing their methods to LUA. Finally, if you have ten elements in an array and the first index is 1, the last one is 10. But if the first index is zero, the last one is nine (length - 1).

2) When defining a function, you are free to define as many arguments as you need, you just need to match this number when you call this function later. Now, when this function is used to subscribe an event like in your example, the event will send by default a given number of args. Here GameEvents.PlayerCityFounded sends three args, the first being the player index and the other ones the city coordinates. If you only need the first one, you can just declare one argument. If you also need the city coords, then you must declare three args in order to retrieve those three values. If you declare four args, the last one will be unassigned (nil).

3) Suppose I want to make an UpgradeCity function that focuses on increasing its pop and adding buildings. That way the event handler (the function you attached to the event) will focus on detecting whether the city was founded on the new continent and, if it is, it will call UpgradeCity. Then I will have two nice small and specialized functions easy to understand when I read them again later in one month. How many args do I need for UpgradeCity? Well, I need the city to upgrade so it could be city coordinates, or it could be a straight pCity object, or a city ID. Once I decide what it will take, I de facto decide how many args it need.

4) I don't understand your question but if you look at the code example I provided I am pretty sure it will be answered.

5) You should use local whenever possible. When you do not use it, a variable or function is defined as global and it causes two problems: first of all it degrades performances (the LUA intepreter needs to do more job before it can find global variables while the local ones are very close and quick to find). Second of all it may conflict with other pieces of code: if two files each have a global "myVariable", this is actually the same variable and when one modifies this value it is modified in all files in your mod. Only use global variables if you want to share data across all your mods' files. Otherwise, use local.
 
Thank you so much Don! I truly appreciate the time you've invested in your reply. It clarifies quite a few things that I've found rather confusing in the online litterature about Lua modding in CIV5. And it puts me better ground to start anew.

Thanks also for the encouragement. I never thought learning all this would be easy, though I have to admit that it's a little less intuitive than I had hoped. Yet, I'm usually not a bad learner and I'm ready to dedicate the needed time to get this working.

Now I've been playing around Firetuner a little. I find it to be quite a amazing source of codes through the edit function. I've been less succesful at using it effectively for debugging purposes however. I'm afraid I'm not looking for the right thing or at the right place. I would have 2 questions regarding this: (1) will the debugging information appear upon loading the Lua file or when the event is triggered in-game, (2) which Firetuner "Lua state" should I look at?

* I don't know how to make the AI move the colonist. Ordering the unit to move is a piece of cake but can we prevent the AI to change the unit's orders? Maybe we must update its orders on every turn. I think this feature can be achieved but it's only a guess and it could be tricky.

You had mentioned this as a reply to my first post about modding a Terra game. I must admit I'm a bit puzzled about it. From your experience, do you think that the AI won't use the new unit at all if it's not forced to? In other words, is there no way to encourage the AI to use Colonist instead of regular Settlers to create cities on different continents?

Regarding the arguments defined within the "()", your explanation clarifies a lot. Yet, my impression is that arguments are left unspecified in many codes I have read (if compared to the way Lua objects are presented on the Civ Wiki or Modding Wiki). When left unspecified won't the game still be looking for those arguments when reading the code?

About Question #4, what I wanted to know is if any Lua script as to be ended with an event or serial event. It may seem commonsensical but my impression was that not all codes were ended that way.

Again, thanks a lot for the time you've taken to read my gibberish codes. Knowing that the CIV modding community is helpful with begginers is clearly a key element in my decision to try modding the game. Now, back to the drawing board! :)
 
Seems like I have been able to figure why I couldn't use Firetuner for debugging, that's some progress... now I still need to be able to understand why things aren't working :blush:

I am trying to use the following function to identify overseas areas. However, there seems to be a missing ')' in the 5th line. I don't understand why and where it should be

Code:
function IsOverseasLocation(pOrigin, pPlot)
	for iPlot = 0, Map.GetNumPlots()-1, 1 do
		local pCapital = City:IsCapital()
		for pCapital = 0, Map.GetNumPlots()-1, 1 do
			if (pOrigin = Map.GetPlotByIndex(pCapital)) ~= (pPlot = Map.GetPlotByIndex(iPlot)) then
			return IsOverseasLocation
			print ("Overseas Location:",plotID, "coords",pPlot:GetX(), pPlot:GetY(), Map:GetPlotByIndex())
			end
		end
	end
end

Also, how do you toggle line numbers in ModBuddy?
 
@Laokong
Yeah, I know you do have the perseverance and capability given how far you've been able to get. :)

Also, LUA and Civ5 modding both have poor documentations, so, for those times you will wonder about it: no, it's not you.

Regarding the Firetuner. Well, seems like you pretty much figured it out that you must look at first panel, the console, I guess you had to modify your ini file (btw, make sure LoggingEnabled and EnableLuaDebugLibrary are both set to 1 to get stack traces). Now, an answer that still may be useful to you: if you look at the four lines example I provided earlier, the last line is executed at the time your LUA file is loaded, as the rest of the file body. So if a bug happens here, it will be displayed in the console right after the map generation script output. However, if a bug happens in the function that is hooked to the event, then the error will appear once the event is fired.


Regarding the colonist, I think it will be used instead of the settler if you define it properly. But you have two problems:
* The AI won't try by itself to settle new continents.
* If both units are purchasable/producable, then I guess only one of them will be ever produced. If the unit spawns from time to time, however, I guess it's not a problem.


Regarding arguments, if a function is defined without any parameter, then you can pass as many arguments as you want, they will never be used. The function just cannot retrieve their values since they do not have a name. So a function uses at most as many args as it defined parameters (I use "arg" for function calls, "param" for function definitions).

Your impression comes from the fact that, indeed, many of the functions you see being defined do not need any parameter. For those many "Update" UI functions, for example, they typically do not need any parameter. If a function's purpose is to refresh the units list, it doesn't need any parameter, it just needs to access the static objects provided by civ5 to enumerate units. Also a few functions use file-level variables, which is sometimes convenient but not recommended (it makes the function hard to read and understand since the informations are scattered throughout the whole file).


About files' ending, there is no rule. Especially no event is required at the end. You can end after a "end", add line breaks or comments after that, you do not even have to define functions, etc, etc.


Regarding your last post:
* You did use assignations (=) instead of equality comparers (==), here is why it fails. Also you cannot have any statement ("print" here) after a return or break: since such a statement cannot be ever executed (the function quits after return), LUA rightfully considers you did a mistake and throws an error. However you could add a statement after the end of your "if" block, it would be ran for every capital but the correct one.
* Here, "City" does not exist. Also I do not understand what you try to achieve.
* Finally, I do not know if you can toggle line numbers in modbuddy (if you can it's in tools > options though) but the line and column are displayed in the status bar.
 
Thanks for your reply Don. Once again, it's very helpful.

My perseverance comes in part from my love of the CIV franchise (including CIV5) and my disappointment with some aspects of CIV5. It was either learn to mod the game or learn another game... which, at some point, would have become repetitive and the cycle would have started over again. Novelty and creativity of simply playing a game tend to wear off quickly and I often have ideas about how to make it better. I came to realize that modding was the only way to maintain my interest in a game, CIV5 is a game worth to invest time and energy and it is rather open to modding. All I need is to learn how ;)

Yes, as you mention, documentation on modding CIV through Lua is a bit insufficient. It seems that there are a few tutorials/references about the most basic aspects of Lua and CIV modding on the one hand, and discussions among skilled programmers about advanced programming notions on the forum. My impression is that an intermediate layer is missing in the documentation, one that would connect the basic tutorials to applied modding.

Actually, my problem with Firetuner came from a xml file in the mod which didn't load properly, thus preventing the whole mod to load. I thought that setting "Import to VFS" on false for all but the file being debugged would be sufficient, but it looks like it wasn't. I've simply created an empty mod to test Lua functions separately. It won't work once my Lua files refer to xml statements, but it'll help me learn at this point. By the way, thanks for pointing at the status bar. For all the time I've spent trying to find an option to show the numbering I just hadn't noticed that it was all showing down there :blush:

Regarding the colonist, I think it will be used instead of the settler if you define it properly. But you have two problems:
* The AI won't try by itself to settle new continents.
* If both units are purchasable/producable, then I guess only one of them will be ever produced. If the unit spawns from time to time, however, I guess it's not a problem.
Yes, those are two key issues. As mentioned in my first post on modding Terra, I hope to be able to nudge the AI towards the New World by changing flavors/victory conditions, by giving a few carrots for settling there (the Colonist is one) while making staying in the Old World less rewarding for all Grand Strategies but Cultural Victory. That will clearly require fine-tuning as the mod progresses.

Having 2 similar units coexist is a bit of a conundrum especially as the prereq tech for the Colonist doesn't lead to anything. My hope would be that CIVs following any Grand Strat but Cultural Victory will research Colonialism when they start colonizing the New World while using settlers for the Old World. Again, I'm planning to use flavors to encourage this behavior. If it doesn't work, I might have the mod replace Settlers by Colonists upon researching Colonialism, but due to the big difference in cost, I would have to extend the pop/buildings bonus of Colonist-founded cities to new cities on the Old Continent. That's something I'd prefer to avoid.

Regarding your last post:
* You did use assignations (=) instead of equality comparers (==), here is why it fails. Also you cannot have any statement ("print" here) after a return or break: since such a statement cannot be ever executed (the function quits after return), LUA rightfully considers you did a mistake and throws an error. However you could add a statement after the end of your "if" block, it would be ran for every capital but the correct one.
* Here, "City" does not exist. Also I do not understand what you try to achieve.
* Finally, I do not know if you can toggle line numbers in modbuddy (if you can it's in tools > options though) but the line and column are displayed in the status bar.

What I am trying to do here is to create a function that would allow to discriminate plot/cities/units based on their Continent Id since most changes brought by this mod will apply to overseas colonies and not on the Old Continent. Thus, this function would be the most basic building block of my mod. It would be used to give extra pop and buildings to overseas colonies founded by Colonist, but also to specify bonuses granted by policies on overseas colonies.

If you're ready to bear with me, I'll try to explain what I'm trying to do line by line:

Code:
[COLOR="Lime"]--Create a new function that will compare a given plot to a CIV's capital city in order to determine if the plot is "Overseas"[/COLOR]
function IsOverseasLocation(pOrigin, pPlot)
        [COLOR="Lime"]--Get the plot we wish to evaluate[/COLOR]
	for iPlot = 0, Map.GetNumPlots()-1, 1 do
		[COLOR="Lime"]--Get the Player's point of origin (I guess I also forgot to
specify the player here). View also comment below.[/COLOR]
                local pCapital = City:IsCapital()
		for pCapital = 0, Map.GetNumPlots()-1, 1 do
			[COLOR="Lime"]--Compare the given plot to the point of origin based
on the continent index. If the two have different continent indexes, than we can 
consider that the plot is overseas.[/COLOR]
                        if (pOrigin = Map.GetPlotByIndex(pCapital)) ~= (pPlot = Map.GetPlotByIndex(iPlot)) then
			      [COLOR="Lime"]--I'm not sure if this last step is necessary, 
but I thought it might help when other functions refer to this one[/COLOR]
                              return IsOverseasLocation
			print ("Overseas Location:",plotID, "coords",pPlot:GetX(), pPlot:GetY(), Map:GetPlotByIndex())
			end
		end
	end
end

I would have 2 questions about your comment on "City:IsCapital":

*Basically, I'm not sure I understand how to use Lua Game Objects listed on the Wikis. My impression was that you could use them as-is to bring up elements already defined in the game. Here, "city:IsCapital()" (I realize I shouldn't have capitalized "City" in my code) is true unless otherwise specified, right?

*As noted above, I should have defined the player (How can you get a capital city without having a player... :hammer2: ). Let's say the player was defined, could I have used the following structure?
Code:
local pCapital = pPlayer:city:IsCapital()
(I have seen this structure "param: param:function()" but I'm not sure how to use it)

Once again, thank you very much Don. Hopefully, other wannabe modders will be able to learn a few things from our exchange :hatsoff:
 
Well, there are two families of objects in the civ5 API.
* Static objects like Map. You use them like this: Map.GetPlotByIndex(0). Their methods are invoked through a simple dot (.).
* Instantiable types like plot and city. There are no "plot" and "city" object in lua, they are just types. What you need is an instance of this type. For example through pCity = pPlayer:GetCapitalCity(). Now you have a "city" object named pCity and you can use pCity:GetX() for example. Their methods are invoked through a colon (:).

Note that a part of this explanation is formally incorrect: Invoking a method through ":" just implicitly passes the caller (pCity in pCity:GetX) as the first argument. So you could as well use pCity.GetX(pCity). And you could as well make an object that have both "static" methods (methods that do not require their caller as first arg) and "instance" methods (the opposite). But most objects in the civ5 API have only instance methods (then there are multiple instances and no global object), or only static methods (then they are stored in a global object). So they can be

Also, note that some of the vocabulary I used is appropriate for strongly-typed object languages but no so much in LUA where types (aside of string, int and a few others) and instances are not first-class concepts, since the language is more flexible than this, but rather design patterns developers use. Well, just forget those semantic problems for now.


Now, some problems with your code:
  • Even if a plot is not the capital's plot, it does not mean it is on another continent. It could be the hex right next to it for example.
  • You return the function itself. What you want is to return "true" or "false" (or not return anything, it will be considered as false if you test it with an "if" statement).
  • Maybe you wanted to check whether the plot is in the city's influence zone (city:GetPlotByIndex is used to get the hexes workable by the city) but, again, if the plot is not in this it does not mean it is on another continent.
  • You first use pCapital as a boolean (city:IsCapital() returns true or false), then as a map-wise index, then as a city-wise index. All of that at the same time.
  • The "p" prefix denotes an object (p stands for pointer, a C concept, no need to study it right now, it would be useless in LUA and it's a nightmare for beginners). If you want to use that naming convention ("hungarian notation") where prefixes are prepended to a variable to indicates its name, do it properly: "s" for strings, "i" for integers, "b" for booleans, "p" for objects and tables. But you can also forget this convention and just sticks with "cityIndex" (an integer of course), "city" (a city object), cityName (would be a string), "isCapital" (a boolean), etc...

If we wanted to rewrite your (flawed) logic, it would be this:
Code:
function IsOverseasLocation(pPlayer, pPlot)
   return pPlayer:GetStartingPlot() ~= pPlot
end

This is what you tried to do. But what you need to do, as I said earlier, is to test the plot's area (there is one area per continent, one area per sea, one area per ocean, one area per lake, etc). Like this:
Code:
function IsOverseasLocation(pPlayer, pPlot)
   return pPlayer:GetStartingPlot():GetArea() ~= pPlot:GetArea()
end

However, if there is a two hexes island somewhere in the middle of the ocean, using a plot from this island with this function would return true and this may not be exactly what you want. Maybe you want the bonus to only apply on a specific continent, with a large enough landmass, etc. Then you would need to run more checks regarding the area itself. Note that plot:GetArea() returns the area's ID. You can use plot:Area() to return the Area itself. Map has some functions to get the biggest landmass area and the biggest water area, while Area has some functions to get its size and such.


PS: Ctrl+G in ModBuddy to "go to line N"
PPS: Notepad++ to search across all civ5's lua files at once (hit ctrl+shift+f). Great to get undocumented function's name and see how they are used. Look at the files in the "maps" folder and in "gameplay/lua" for areas functions.
 
Thank you so much!

The first part of your reply makes things much clearer and helps connect what I read about Lua to what I've seen in mods and scenarios.

Now, about the problems with my code. First I got to report a little success, on a recent try I've been to write a working code and to debug it correctly so that the file loaded... and second I had figured
For example through pCity = pPlayer:GetCapitalCity(). Now you have a "city" object named pCity and you can use pCity:GetX() for example.
all by myself! There are no small victories :)

Of course, as you pointed out I had misunderstood the role of "city:GetPlotByIndex". Hence, the code would have informed me that the selected hex wasn't the hex of the Capital... an information of "capital" value if I may...
I'll try again with "pPlot:GetArea()".

You first use pCapital as a boolean (city:IsCapital() returns true or false), then as a map-wise index, then as a city-wise index. All of that at the same time.

Yes, I can see how that may cause problems. My (wrong) understanding was that:

First, I needed to define what pCapital was, hence
local pCapital = City:IsCapital()​
but now I believe the right way would be:
local pCapital = pPlayer:GetCapitalCity()​

Second, I thought I needed to establish where the capital was located in order to get the info about continents, that's why I had:
for pCapital = 0, Map.GetNumPlots()-1, 1 do​

Third, in order to compare the capital and the plot to be evaluated, I thought I could compare the two by using:
if (pOrigin = Map.GetPlotByIndex(pCapital)) ~= (pPlot = Map.GetPlotByIndex(iPlot)) then​
If I understand your comment correctly, in order to do this I would have had to define a new object for each of these lines, right?

Regarding the issue of oceanic islands. At this point, given my difficulties, I don't think I will make the code even more complex by specifying landmasses. After all, some islands were used historically as stepping stones towards larger colonies (Azores, Cape Verde) or as colonies in their own right (Caribbean Islands). The only troublesome issue would be in the rare instance where 2 oceanic islands belonging to different continents are connected by coastal waters. So far, I've only seen that happen once on Terra maps.

One more question: once I have a working code for identifying overseas plots, how can I test it in Firetuner? Is there an "hex picker" that would attempt to run my script?

Oh, and thanks for the software references!
 
Hmm, now I'm confused (again?): the more I think about it, the more the function you provided in your previous post seems complete.

Code:
function IsOverseasLocation(pPlayer, pPlot)
   return pPlayer:GetStartingPlot():GetArea() ~= pPlot:GetArea()
end

Is that so? Are those 3 lines doing everything I've been trying to do in a convoluted and inefficient way?

In other words, am I still trying to learn how to bait the line while you've already given me the proverbial fish? ;)
 
Now that, thanks to Don, I have a simple way to establish which areas are located overseas. I have tried to add the second part of my Colonist script. For the sake of simplicity, I've been testing/debugging the code using regular Settlers instead of my new Colonist unit.

The Lua file loads properly, debugging doesn't show any problem yet the script doesn't do what I was hoping it would:
(a) Prevent settling new cities on the continent of origin.
(b) Print a statement when an overseas city is founded.
Any idea?

Code:
function IsOverseasLocation(pPlayer, pPlot)
	return pPlayer:GetStartingPlot():GetArea() ~= pPlot:GetArea()
end

--Modeled on Population Mod
function ColonistCanFound(pPlayer, pUnit, pPlot)
	for iPlayer=0, GameDefines.MAX_MAJOR_CIVS-1 do
		local pPlayer= Players[iPlayer]
		local pUnit = pPlayer:GetUnitByID(iUnit)
	
		if (pUnit == nil or pUnit:IsDelayedDeath()) then
			return
		end
	
		if (pUnit:GetUnitType() == GameInfoTypes["UNIT_SETTLER"]) then
		local pPlot = pUnit:LastMissionPlot()
	
		if pPlot ~= IsOverseasLocation(pPlayer, pPlot) then
			pUnit:canFound()
			print ("Overseas colony founded by colonist")
			end		
		else 
			print ("Colonist can't found new cities on continent of origin")
			end			
		end
	end
Events.SerialEventCityCreated.Add(CityFoundedByColonist)
 
Message #9 and #10
Indeed, the code I provided may be totally suitable as it is. One problem though: Right now the code just test whether it is on the continent the player started on But if players can start on different areas, then you will probably want to modify it to be sure that the plot's area is not the starting continent's area of any civ. Basically you would need to wrap the line of code into a for loop that would scan all player (and you would not have anymore to provide a player as argument).

Regarding the variables problem, there are three of them:
  • You could assign a bool to a variable, test this variable, then assign an index to it, provided you do not need the boolean anymore. It works but it's dirty, it's better to have one variable per role and to use proper names for variables. Also pCapital was an index while with such a name one expects a city objects.
  • The bool was never used. You did the same thing in your last message: you provide a pPlayer as an argument. Than you do not use this value and you redeclare a local variable with the same name, which makes the argument unreachable. So the player argument is never used.
  • Back to the original code (post #7): pCapital was being redefined as an index but it was enumerated on the whole map (up to Map.GetNumPlots -1) while city:GetPlotByIndex was expecting something up to city:GetNumPlots - 1. This is what I meant earlier with map-wise index and city-wise index. They are both integer values but you should rather see them as two different types: a city-wise index and a map-wise index.

The following code should implement a hex picker(untested):
Code:
-- Registers an event handler invoked anytime the mouse or keyboard are used.
-- The arguments probably look at little strange, they come the Windows API designed in the 90's for C code.
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 plot = Map.GetPlot(UI.GetMouseOverHex())  -- Clicked plot
   YourFunctionToBeCalled(plot)
end
ContextPtr:SetInputHandler(InputHandler);



Message #11
The code should print errors at the very beginning. Not after that: since there was errors before that point, the event was never registered and your code never runs. Try to add some print statements at the beginning of your file, make sure they are displayed in Firetuner and notice when they are.The error should come just after that. If nothing is displayed, check the ini file settings I talked about earlier.

The problems:
  • The way you use "end" is incorrect. Especially you must not use one between "if" and "else" (correct schema: if elseif elseif elseif else end). And finally there is no "end" for your function block, you need to add one.
  • Also your indentation is bad and it gives the impression you added two end after "else" while the second one actually ends an upper "if" statement
  • IsOverseasLocation returns a boolean, but you compare it to a plot object, the result will always be false. What you need to write is: "if IsOverseasLocation(pPlayer, pPlot) then"
  • pUnit:CanFound() should be returned. So use "return pUnit:CanFound()". Move the print statement before it.

One more big problem:
  • You provided a pUnit argument, which is fine. But then you redeclare this pUnit by using a non-existent "iUnit". You also did the same thing with pPlayer (provided but never used) and an non-existent "iPlayer". There is obviously something you did not understand correctly yet.
  • ColonistCanFound should indeed accept a pPlayer, a pUnit and a Plot, this is nice. If you get those as arguments, you do not have to try to get them in your code from non-existent variables. But SerialEventCityCreated is going to provide you a hexpos, a playerID and a cityID. Now is your function meant to be invoked from SerialEventCityCreated? No, this is just a test. Either you create another function that will make the interface between the event and your function, either you temporarily modify your function for testing purposes, so that it can be called directly by the event.
  • I guess all those troubles you have come from the fact you don't know how to get the data you need. So here are some code lines. They are not meant to be used directly, they're examples of what you can use depending on how you want to make your plumbing.
Code:
--Get the active player ID
local playerID = Game.GetActivePlayer()
--Get a player object from a player ID
local pPlayer = Players[playerID]
-- Get a unit object from a unit ID (I am not sure about this one)
local pUnit = pPlayer:GetUnitByID(unitID)
-- Get the first ready unit object for this player.
local pUnit = pPlayer:GetFirstReadyUnit()
-- Hexpos to plot
local gridPosX, gridPosY = ToGridFromHex( hexPos.x, hexPos.y );
local plot = Map.GetPlot( gridPosX, gridPosY );

That being said, you do a good job so far, your progression is visible. ;)
 
It's always a great pleasure to turn on the computer and see one of your detailed replies Don. I can't express how much I appreciate your help. :worship:

If I may, I'll go through your most recent post paragraph by paragraph since I have a few questions about most of its content (I'll wrap the elements as spoilers so the length remains manageable).

1- function IsOverseasLocation(pPlayer, pPlot)
Spoiler :
Right now the code just test whether it is on the continent the player started on But if players can start on different areas, then you will probably want to modify it to be sure that the plot's area is not the starting continent's area of any civ. Basically you would need to wrap the line of code into a for loop that would scan all player (and you would not have anymore to provide a player as argument).

Basically on a Terra map that shouldn't be that much of an issue since all CIVs start in the same Eurasian-Africa continent. However, if I am able to implement in my mod all the elements I have in mind, that may become a problem. In addition, if the mod reaches its goals, we may see a CIV surviving on a new continent while it's possessions on the continent of origin are conquered. What if this CIV were to try to colonize a sub-continent (Terra "New World" usually has 1-3 different areas) ? That's one reason I had been working as the Capital as the point of origin.

Would the following work to scan all players on the basis of their capital?
Code:
--Untested
function IsOverseasLocation(iPlayer, pPlot)
	for iPlayer=0, GameDefines.MAX_MAJOR_CIVS-1 do
	local pPlayer = Players[iPlayer]
		if (pPlayer:IsAlive()) then
			local capital = pPlayer:GetCapitalCity()
			print ("Overseas Location:",plotID, "coords",pPlot:GetX(), pPlot:GetY(), Map:GetPlotByIndex())
			return capital:GetArea() ~= pPlot:GetArea()
		end
	end
end
2- About the hex picker
In order to use it should I create a new button in Firetuner and then use your code to define the button with the "Edit" function?

3- Printing errors at the beginning
Spoiler :
The code should print errors at the very beginning. Not after that: since there was errors before that point, the event was never registered and your code never runs. Try to add some print statements at the beginning of your file, make sure they are displayed in Firetuner and notice when they are.The error should come just after that. If nothing is displayed, check the ini file settings I talked about earlier.
I do have a message printing at the beginning of my test file, but, er... it's a bit silly and I don't include it on the forum... :blush:
It comes from the little Lua tutorial on the CIV Wiki:
Code:
if Players[Game.GetActivePlayer()]:GetCivilizationType() == GameInfo.Civilizations["CIVILIZATION_EGYPT"].ID then 
	print("Walk like an Egyptian") 
end
To debug I always start a new game with Ramses... and it's always a pleasure to see that quote in Firetuner :D

4-The Problems:
Spoiler :
  • The way you use "end" is incorrect. Especially you must not use one between "if" and "else" (correct schema: if elseif elseif elseif else end). And finally there is no "end" for your function block, you need to add one.
  • Also your indentation is bad and it gives the impression you added two end after "else" while the second one actually ends an upper "if" statement
  • IsOverseasLocation returns a boolean, but you compare it to a plot object, the result will always be false. What you need to write is: "if IsOverseasLocation(pPlayer, pPlot) then"
  • pUnit:CanFound() should be returned. So use "return pUnit:CanFound()". Move the print statement before it.
Yes, I believe the problem of "end" and indentation are related. Yesterday when I was working on this part of the code I kept getting errors in Firetuner. After a few permutations I ended up with the structure I have posted, which I also felt was incorrect but didn't return end-related errors. As you indicate, bad indentation might have led me astray.
I am a bit confused about when to and not to use "return". While working on the code I went back to the Lua manual which states:
A return statement returns occasional results from a function or simply finishes a function. There is an implicit return at the end of any function, so you do not need to use one if your function ends naturally, without returning any value.
In my case, I do realize now that I needed to return a result, however when does a "function ends naturally, without returning any value"?

5- Concerning notation
Spoiler :
(From Post #8) The "p" prefix denotes an object (p stands for pointer, a C concept, no need to study it right now, it would be useless in LUA and it's a nightmare for beginners). If you want to use that naming convention ("hungarian notation") where prefixes are prepended to a variable to indicates its name, do it properly: "s" for strings, "i" for integers, "b" for booleans, "p" for objects and tables. But you can also forget this convention and just sticks with "cityIndex" (an integer of course), "city" (a city object), cityName (would be a string), "isCapital" (a boolean), etc...
You provided a pUnit argument, which is fine. But then you redeclare this pUnit by using a non-existent "iUnit". You also did the same thing with pPlayer (provided but never used) and an non-existent "iPlayer". There is obviously something you did not understand correctly yet.
Yes, this is a big issue, I don't totally understand when to use what. More importantly, in light of the way most codes (game Lua files, scenarios, mods, etc.) are built, I'm worried that not using iUnit, pUnit, etc will make the code unreadable for the game engine. I've read a bit on the forums about notation, that and your comments here lead me to believe that excepting already defined Lua game objects, everything else (args, params, etc) can be set by the modder. Am I right?

For example:
Code:
function AddPapaSmurf(objectSmurf, objectSmurfVillage)
	for indexSmurf = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
		local objectSmurf = Players[indexSmurf];
		if (objectSmurf ~= nil) then
			if (objectSmurf:IsAlive()) then
				local objectSmurfVillage = objectSmurf:GetStartingPlot();
				if (objectSmurfVillage ~= nil) then
					objectSmurf:AddFreeUnit (GameInfo.Units["UNIT_PAPASMURF"].ID)
				end
			end
		end
	end
end
ColonistCanFound should indeed accept a pPlayer, a pUnit and a Plot, this is nice. If you get those as arguments, you do not have to try to get them in your code from non-existent variables.
But I would still need to define them as local variables by reference to some index as in "local objectSmurf = Players[indexSmurf];" above, right?

If I'm right then, should the arguments of the above function be "function AddPapaSmurf(indexSmurf, objectSmurfVillage)" and not "function AddPapaSmurf(objectSmurf, objectSmurfVillage)"?

6- SerialEventCityCreated
Spoiler :
But SerialEventCityCreated is going to provide you a hexpos, a playerID and a cityID. Now is your function meant to be invoked from SerialEventCityCreated? No, this is just a test. Either you create another function that will make the interface between the event and your function, either you temporarily modify your function for testing purposes, so that it can be called directly by the event.
Hmm, I see the issue here.
First, I started with the assumption that the function was a precondition to the SerialEvent at the end of the code. Now that I've been playing around with modding and debugging, I understand that the SerialEvent at the end is instead the event that will invoke the above function(s). Am I right? If this is the case, I won't need to add it at the end of my final code since my goal here isn't to modify the creation of the city (unless I need to include it in order for my bonuses to colonies to be implemented?).

Second, I still need a way to test my function as you indicate. If I understand well, if I don't have such an event the function won't "print" in debugging, right? I have to admit I don't really know what to do about this problem. Would it be sufficient to just add the arguments required by the SerialEventCityCreated without using them anywhere in the code?

7-Concluding notes
Spoiler :
I guess all those troubles you have come from the fact you don't know how to get the data you need. So here are some code lines. They are not meant to be used directly, they're examples of what you can use depending on how you want to make your plumbing.
Yes, it's a problem. I still have to figure out which object I need to use and when, and what kind of data it will return. I find my inspiration in other mods and Lua files packaged with the game/DLCs, but I often misunderstand the type of data (index, boolean, integer) these codes return and thus different lines may work at cross-purpose. I try to follow the Game Objects lists as closely as possible, but they're often incomplete. Hopefully, using Notepad++ to find the way each are use might help avoid those basic mistakes.

That being said, you do a good job so far, your progression is visible.
Thanks! And again, thank you so much for taking the time
 
You're welcome, it's a pleasure to see someone learn programming by himself as I did a long time ago. ;)

1 - IsOverseasLocation
This code is headed in the right direction but it has a few problems:
* pCapital is a city object and cities do not have a Area method. You must first retrieve the city's plot through city:Plot(). For example: local capitalPlot = pPlayer:GetCapitalCity():Plot()
* However players do not always have a capital (the beginning of the game) and their original capital may have been captured. So it's better to use pPlayer:GetStartingPlot() to get the starting plot rather than the capital's plot.
* The biggest problem now: you always return a value. Which means your function will always end after the first alive player. Instead return false if any of the players is on the same area. Then add a "return true" at the very end of the function: if the code ever reaches this point, it will be because none of the players returned false. The "true if true for all items" problem typically translate into algorithms like (this is pseudo-code, not LUA):
Code:
for every item
   if this item does not match then return false
end
return true
Those kind of things will soon become a reflex for you.

2 - Hex picker
Just add it to your code, not in Firetuner. Your function will be invoked anytime you click a hex with the mouse.

4 - Return statements
A function ends naturally if it reaches its last line. This occurs if it never returned a value and no error was thrown. I guess you understand that better after what I discussed in point #1.
Also, an interesting thing: if a function ends naturally it returns nothing. Nothing will behave as "nil". So if you assign a variable with this result, it will contains "nil".
More interesting stuff: if you perform a logical test (through an "if" statement for example), "nil" and nothing are like "false" while everything else is like "true" (including 0 and the empty string).

5 - Notation
You are correct: you can freely name your variables (except keywords like "return", "end", etc, although you can use them with different character cases, like If or Return), you do not have to use prefixes and you can even use wrong prefixes. It's "just" a matter of being able to write a clean code you will have pleasure to revisit in a few weeks. That being said, most of a dev's effort involves reading, understand and modifying existing code, and you will sometimes hate yourself for what you wrote a few weeks or months ago. ^^ Also, code readability is very important anytime other developers work with you, from open source projects to almost every professional environment. So elegance and readability are far more important than you may suspect at that point.

Now, when it comes to the hungarian notation (the one with prefixes that Firaxis used), the rule in civ5 is simple: "i" for integers, "b" for booleans, "s" for string, "p" for the rest (tables, API objects, etc).


The most important point now: no, if your function get an argument as pPlayer, you do NOT need to have a corresponding local index. What would be the point? You already have your object, the function's caller provided it to you. The object is ready to use, just use it. :)

It's up to you to decide whether you want your function to require indices or objects. If you get indices, you often have to retrieve the objects from the indices in your function since you can't do much with plain indices. But at some point in your code you need to do that anyway since the civ events and API often provide mere indices. So some function will have to do that. However once it has been done, it's typically easier to have this function pass the retrieved objects to other functions (callees) rather than pass the indices and force the callees to again retrieve the objects from them.

In the end, most of the time you just need to decide who will bother retrieving the objects from the indices. Once it is done, provide objects, it's easier.

6 - SerialEventCityCreated
I discussed that point earlier, let me rephrase it:
* This event is the best one to apply the bonuses you want. And if in the end you remove the colonist unit and keep settlers, you will probably only need this event.
* However if you keep the colonist, you probably cannot use this event to know if the city was founded by a colonist rather than a settler since the unit will have been probably removed from the game. So you would need to use another event that would occur before the city is created. Of course you could not use such an event to apply the bonuses you need since the city would not be created yet.
* That is, if you want a colonist unit, you need a two-steps algorithms, one where you will store the colonist position on file-level variable, then a second one, later where you will check whether the city was founded by a colonist and apply the bonuses.
* And on top of that you need other events: one to prevent a colonist to found a colony if not on an overseas plot, one to force the AI to move the colonist to overseas land, etc.

7 - Concluding notes
Indeed, notepad++ and its "search all files" feature is a great help for modders since the source code is the main documentation source. However I am currently polishing a tool that scans the 2k games wiki, the binaries, the output from a custom mod and that parses the whole source code in order to automatically generate and upload a complete wiki on CivFanatics. The results are very satisfying and I am certain that despite the lack of manual comments, it will be a great help for every modder. :)
 
Hi again!
It's still not working properly but I feel like I'm making some progress: I got the function to print! :woohoo:

Alright, alright, I'll keep my calm ;)

1-IsOverseasLocation
Hmm, yes, I had not thought that in the game the capital referred to the original one (due to the conditions behind the Domination Victory I guess) not to the current capital. Is there a way to bring up the current capital?
As for:
The biggest problem now: you always return a value. Which means your function will always end after the first alive player. Instead return false if any of the players is on the same area. Then add a "return true" at the very end of the function: if the code ever reaches this point, it will be because none of the players returned false.
Could I use
"local pPlayer = Players[Game.GetActivePlayer()]"​
Instead of:
local pPlayer = Players[iPlayer]
if (pPlayer:IsAlive()) then​

2 - Hex picker
I haven't tried it yet since I didn't know where to put the code.

4 - Return statements & 5 - Notation
I think I am slowly getting a grasp on these elements. Thanks again for your patience.
Since I believe Smurfs do not make up a large contingent of the modding community, I'll learn how to use Hungarian ;)

One thing that confuses me though is ""i" for integers". When it is used as an argument of a function (as it is in many codes) is it because it refers to a number somewhere in the game which defines that element (i.e. iUnit would refer to a number identifying a Unit)?
*edit: Oh, now I get it (I think). "i" is used when we need to define something in relation to a game object which return an integer as in "for iPlot = 0, Map.GetNumPlots()-1, 1 do". I have to get rid of the idea that the arguments in the function refer to something outside the function...

6 - SerialEventCityCreated
Here comes my little victory! I used "GameEvents.UnitSetXY.Add(ColonistCanFound)" to test my function and it printed when I founded a new city (except for the capital)! Of course, it printed the message as many times as units I had in play. I guess it's because I hadn't define a "iUnitID" which the event expected.

Now, the problem I have is that the function still can't discriminate based on continents. For both my tests (plopping a settler on the continent of origin --> settling city, then on the New Continent) it printed "Colonist can't found new cities on continent of origin". Yet, the city was founded anyway. Would I need to activate the "--pUnit:canFound(false)" in my code to prevent them from founding cities? Another source of the problem might be using "Unit:canFound" instead of "Unit:isFound" I'm still unsure which one would be needed here.

In his "UI - Overlay Continents (v 5)" mod, whoward69 uses "pPlot:GetContinentArtType()" instead of "GetArea" to highlight different continents. Is it possible that GetArea doesn't work as intended? Or is there some other mistake in my code.

Here is the latest version of the code:
Code:
function IsOverseasLocation(pPlayer, pPlot)
	return pPlayer:GetStartingPlot():GetArea() ~= pPlot:GetArea()
end

--Modeled on Population Mod
function ColonistCanFound(iPlayer, pUnit, pPlot)
	for iPlayer=0, GameDefines.MAX_MAJOR_CIVS-1 do
		local pPlayer= Players[iPlayer]
		local pUnit = pPlayer:GetUnitByID()
	
		if (pUnit == nil or pUnit:IsDelayedDeath()) then
			return
	end
	
		if (pUnit:GetUnitType() == GameInfoTypes["UNIT_SETTLER"]) then
		local pPlot = pUnit:LastMissionPlot()
	
		if IsOverseasLocation(pPlayer, pPlot) then
			print ("Overseas colony founded by colonist")
			return pUnit:canFound()
					
			else 
				--pUnit:canFound(false)
				print ("Colonist can't found new cities on continent of origin")
			end
		end
	end
end
GameEvents.UnitSetXY.Add(ColonistCanFound)
 
So, given the problem I still had with continent identification. I was forced to take a rather dispiriting step back and try different approaches while testing them with your hex picker. So far, it has been a failure :(

Here are the functions I have tested and their results. All have been tested twice: first at turn = 1 and second at t=2. I had to do this in case some variables were defined by the game once the capital had been found.

Code:
function IsOverseasLocation(pPlayer, pPlot)
   return pPlayer:GetStartingPlot():GetArea() ~= pPlot:GetArea() [COLOR="Red"]<--[/COLOR]
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 plot = Map.GetPlot(UI.GetMouseOverHex())  -- Clicked plot
		if IsOverseasLocation(pPlayer, pPlot) then
		print "Overseas Location"
	end
end
ContextPtr:SetInputHandler(InputHandler);

this one returned an error at line 2: attempt to index local 'pPlayer' (a nil value)


Code:
function IsOverseasLocation(pPlayer, pPlot)
	for iPlot = 0, Map.GetNumPlots()-1, 1 do
	local pPlot = Map.GetPlotByIndex(iPlot)
		local pPlayer = Players[Game.GetActivePlayer()]
		return pPlayer:GetStartingPlot():GetArea() ~= pPlot:GetArea()
	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 plot = Map.GetPlot(UI.GetMouseOverHex())  -- Clicked plot
		if IsOverseasLocation(pPlayer, pPlot) then
		print "Overseas Location"
	end
end
ContextPtr:SetInputHandler(InputHandler);

This one printed "Overseas Location" regardless of the continent clicked.


Code:
function IsOverseasLocation(pOrigin, pPlot)
--or function IsOverseasLocation(iOriginPlot, iPlot)
	for iPlot = 0, Map.GetNumPlots()-1, 1 do
	local pPlot = Map.GetPlotByIndex(iPlot)
	local pPlayer = Players[Game.GetActivePlayer()]
	local iOriginPlot = pPlayer:GetStartingPlot()
		for iOriginPlot = 0, Map.GetNumPlots()-1, 1 do
		local pOrigin = Map.GetPlotByIndex(iOriginPlot)
		
		return pOrigin:GetContinentArtType() ~= pPlot:GetContinentArtType()
		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 plot = Map.GetPlot(UI.GetMouseOverHex())  -- Clicked plot
		if IsOverseasLocation(pOrigin, pPlot) then
		print "Overseas Location"
	end
end
ContextPtr:SetInputHandler(InputHandler);
or
Code:
function IsOverseasLocation(iPlot, iOriginPlot)
	for iPlot = 0, Map.GetNumPlots()-1, 1 do
	local pPlot = Map.GetPlotByIndex(iPlot)
	
	local pPlayer = Players[Game.GetActivePlayer()]
	local iOriginPlot = pPlayer:GetStartingPlot()
	local pOrigin = Map.GetPlotByIndex(iOriginPlot)
		
	return pOrigin:GetContinentArtType() ~= pPlot:GetContinentArtType()
	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 plot = Map.GetPlot(UI.GetMouseOverHex())  -- Clicked plot
		if IsOverseasLocation(iPlot, iOriginPlot) then
		print "Overseas Location"
	end
end
ContextPtr:SetInputHandler(InputHandler);
Both wouldn't print anything when clicking. I guess I made an error somewhere but couldn't figure it out.

Code:
function IsOverseasLocation(pPlot, pOrigin)
	for iPlot = 0, Map.GetNumPlots()-1, 1 do
	local pPlot = Map.GetPlotByIndex(iPlot)
	
	local pPlayer = Players[Game.GetActivePlayer()]
	local capitalPlot = pPlayer:GetCapitalCity():Plot() [COLOR="Red"]<--[/COLOR]
	local pOrigin = Map.GetPlotByIndex(capitalPlot)
		
	return pOrigin:GetContinentArtType() ~= pPlot:GetContinentArtType()
	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 plot = Map.GetPlot(UI.GetMouseOverHex())  -- Clicked plot
		if IsOverseasLocation(pPlot, pOrigin) then
		print "Overseas Location"
	end
end
ContextPtr:SetInputHandler(InputHandler);
This one returned an error on line 6: "attempt to index a nil value" the first time I tried. Understandably so since I clicked on a hex before settling the capital. However, when I tried again with an existing capital, I couldn't get anything to print. :cry:
 
Message #15
Actually, GetCapital returns the current capital, not your original one. Now, you modified the IsOverseasLocation so that it would test whether the actual plot is not on the starting continent of any civilization. If you were to do the modification you suggested, then you would only test if the plot is not on your starting continent.

About iUnit... Well, it could mean two things.
* It could either be the unit's type ID, as defined in Units.xml. So 0 could be settler, 1 could be worker, etc (settlers and units actually have different ID than those).
* Or iUnit could be the unit index as returned by Unit:GetID() or when enumerating from 0 to player:GetNumUnits() - 1, then using player:GetUnitByID(iUnit)

I don't know if you can prevent a settler to found a city, but if you can you need to use GameEvents.PlayerCityFounded (see the Steampunk scenarion bundled with G&K). However it will only be useful for human players, for the AI you will need to give orders to the unit instead.

Finally, continent art styles are used to introduce variations in the graphics, so that not all mountains look the same, not all forests look the same, etc. Four flavors exist: Asia, America, Africa, Europe. The default art stamper uses one art style per continent. But some maps (like PerfectWorld I think) can use custom stampers and insert African-styled mountains in the middle of an European-styled continent. So both continents identification methods (through area ID or continent art styles) have their flaws. Area ID look at landmasses, so Great Britain has a different ID from Europe, although it is considered as a part of Europe. It would not happen with the continent art styles and the default stamper but this method would totally fail should a map mixes continent art styles within a same continent (two neighbor plots could then be considered to be on different continents). I suggest you ask Whoward why he took that way since he may have something to add I do not know on this topic.



Message #16
All your tests have the same problems: InputHandler provides at least one more nil value. For example if IsOverseasLocation requests a pPlayer, then you must retrieve the pPlayer in InputHandler (through local pPlayer = Players[Game.GetActivePlayer()]) then provide that value to IsOverseasLocation when you call it. Look at test #1: you declare and initialize a "plot" variable, but then you provide non-existent pPlayer and pPlot. It's like calling IsOverseasLocation(nil, nil) or IsOverseasLocation().

As a result, in test #2 to #5, you redeclare your parameters in the function, probably because you got errors that resulted from the first problem. But it doesn't make sense: if you intended to redeclare those variables in your function, then you didn't need the caller to provide you parameters in the first place, so why define your function with those parameters? All functions in test #2 to test #5 could be as well defined without any parameter.

Now, in test #2 to #5, you made a logical fault: you scan all plots but return at the first iteration. So here are what your functions really do:
* Test#2: returns whether the (0,0) plot is on the starting area of the player.
* Test#3: returns whether (0,0) has the same art style than (0,0)
* Test#4: returns whether (0,0) has the same art style than the player's starting plot.
* Test#5: returns whether (0,0) has the same art style than the player's capital's plot.

Finally, you used indentation wrong, which makes your code hard to read. You must indent *after* an "if" or a "for". Here are examples:
Code:
for .....
    local snurf = ....
    if .... then
        local schmurtz = ...
        RunSomeFunc(snurf, schmurtz)
        for ... then
             RunOtherFunc()
        end
    end
end

Yep, the first steps in learning how to write code are hard. ;)
 
1-IsOverseasLocation
Actually, GetCapital returns the current capital, not your original one. Now, you modified the IsOverseasLocation so that it would test whether the actual plot is not on the starting continent of any civilization. If you were to do the modification you suggested, then you would only test if the plot is not on your starting continent.
Oh, yes, and of course, there are always Minor Civs starting somewhere. Hence the positive returns everywhere.
Now, on a previous post you indicated that:
The biggest problem now: you always return a value. Which means your function will always end after the first alive player. Instead return false if any of the players is on the same area. Then add a "return true" at the very end of the function: if the code ever reaches this point, it will be because none of the players returned false.
I'm not sure exactly how to do it. Would the following work in the way you described?

Code:
--Untested
function IsOverseasLocation(iPlayer, pPlot)
	for iPlayer=0, GameDefines.MAX_MAJOR_CIVS-1 do
	local pPlayer = Players[iPlayer]
		if not (pPlayer:IsAlive()) then
			return
		else       
			local capitalPlot = pPlayer:GetStartingPlot()
			print ("Overseas Location:")
			return capitalPlot:GetArea() ~= pPlot:GetArea()
		end
	end
end

2-Settlers/Colonists
I don't know if you can prevent a settler to found a city, but if you can you need to use GameEvents.PlayerCityFounded (see the Steampunk scenarion bundled with G&K). However it will only be useful for human players, for the AI you will need to give orders to the unit instead.
Yes, I had my doubt about it too since I believe there's a lot about settlers that is hidden in the source code. Ultimately, I don't plan to prevent settlers from settling anywhere, Colonialism and Colonist would remain optional tech/units for Civs that are not aiming at a cultural victory. So far I've been using settlers so that I could test the Lua code by itself, but, yes, I might have to start using Colonist in testing because of those doubts about settlers.

3-Area vs. ContinentArtStyle
I see. Once I have a working code, I might have to test the two approaches and see which one is the most effective on a Terra map. I was inclined to think in terms of ArtStyle because I had tried the "UI - Overlay Continents (v 5)" mod on Terra maps and it worked well to identify continents.

4-InputHandler Testing
I think the problem comes from the fact that I'm unsure how much information gets transfered from one function to the next within the same code. From what you've written I guess my view was too linear: the engine would go through the 1st function of the code then move to the next while storing the info obtained at the first one, for instance what hex and player were selected. Now that I think about it, I see that clicking on a hex triggers the second function first, which then invoke the first one to run my test. I need to stop thinking in terms of "narrative" and more in terms of "code". :crazyeye:

5- Indentation
Ok, so indents after a "if" or a "for", not at the "if" or "for" like I tended to do. What about a series of Local definitions? I would say that since they don't require an "end" there is no need for indentation (maybe a new line)?

Alright, going back to testing!
 
Indentation and visibility scopes
Indeed, there is no need to indent after a local definition. The first reason is that indention is used to emphasize blocks of code that may or may not be executed, or may be executed many times, or will be executed later, etc. They are disruptions of the instructions flow. This is why we indent after "if", "for" or a function declaration. However since the code after a local definition does not create a new block of code (instructions after it are executed after it), there is no need to indent.

Also, blocks define visibility scopes. So what the hell is a visibility scope? Well, it's simple: anytime you define a variable, you do so in the current scope. And anytime you try to read a variable, LUA search in the current scope and its parents. But not in the children scope. AN example:
Code:
local u = 1
-- Point alpha

function A()
   local v = 2
   -- Point beta
   for i = 0, 5, 1 do
       local w = 3
       -- Point gamma
       B(v)
   end 
   -- Point delta
end

function B(x)
    -- Point epsilon
    local v = 5
    -- Point zeta
    local y = 6
end
* At point alpha, you can see variables u (1)
* At point beta, you can see u (1) and v (2).
* At point gamma, you can see u (1), v (2), w (3), i (0 to 5). Indeed, "i" is in the "for" scope.
* At point delta, you can see u (1), v (2). But not "i" nor "w".

* At point epsilon you can see x (2, the value of v in A). But not u, v and w.
* At point zeta you can see x (2) and v (5). Note that v is still 2 in function A, those are different variables. You still cannot see "y": it's in the same scope then "v" and "x" but it's not defined yet.

If you try to read a variable from a point where it is not visible, you will get "nil", because the variable is not defined. 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.


IsOverseasLocation
Nope, that won't do. You will return after the first civ you tested. You will return nothing if the first player was dead, or if it was alive you will return whether his starting plot is on the same area than the plot you provided as as argument. Also, again, you provide an iPlayer and you don't use it but instead redefine it. Remember what I wrote earlier:
Code:
for every item
   if this item does not match then return false
end
return true
 
Back
Top Bottom