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

Regarding the performances, there are different ways to answer you.

1. Anonymous function are not faster than stand-alone functions. They provide the exact same results (well, I guess there is a marginal benefit since some variables are in a closer scope than they would be with a stand-alone function using file-scoped locals but it's negligible).

2. LUA is a weakly typed language, so it's slow as hell. But slow as hell on our modern, very fast CPU, is still very good. With a strongly typed language, I start to slightly worry when some simple function like yours is called more than 100k times per frame (one frame = 15ms = 20M language-level operations). Even if lua is slow, your function will take far less than one micro-second. And since the AI probably never moves more than 1k units per second, you will be below 0.1% of CPU time.

3. There is a thing called "premature optimization" and it is usually followed by "is the root of all evil". Programmers do not know what's going to be slow, programmers do not know what they need to optimize, programmers need to rely on measures. Optimizations must be usually done either at the prototyping stage (to ensure your idea is achievable or because you could have to choose specific architectures based on performances) or at the later ones (polishing the product). Focus on code quality, not on performances. Especially, most beginners tend to optimize things that do not need to be optimized ("how proud I am of this neat optimization that saves 1ms per year and totally wasted my code, made it unmaintainable, unextendable and unreadable, required me three days of development and introduced ten bugs in something that used to work fine"). Some of those young programmers seem to have an optimization compulsive disorder: anytime they see a perfectly fine code that could be made faster, they do it. Then project managers come and reject all those submissions for the sake of the project. Maybe it's faster but if it's negligible or irrelevant (there was no problem), then even a single additional line of code is too much of a price. We probably all went through that stage ; the earlier we quit it, the better.

4. That being said, it's not a reason to do things dirty. Besides code quality also means that you must do things at the moment one expect them to be. Why would you check the colonist's location on every move if you only need this information when the player founds a city? Choose the elegant way, as always: put that code in the proper event where it is supposed to be.
 
Thanks for the clarifications about performance. I wasn't too worried, yet the amount of info printed (which of course won't be printed once debugging is done) appeared staggering even on Turn #2. However, as you mention on your point #4 I might end up embedding the Area check only within Functions that truly require it. In the meantime, building that function and seeing it work was truly educative.

Nevertheless, the way I fixed my "caller" issue (by modifying GameEvents.UnitSetXY.Add(isOverseasLocation)) kinda puts me in a bind for 2 reasons.

1- Functions without callers
A great many functions found in mods and scenarios don't modify Game Events. See an example taken from the Medieval Scenario:
Spoiler :
Code:
function AddInitialUnits(iPlayer)
	
	local pPlayer = Players[iPlayer];
	if (pPlayer:IsAlive()) then
		local capital = pPlayer:GetCapitalCity();
	
		-- Historical map
		if (capital ~= nil) then

			iUnitID = GameInfoTypes["UNIT_WORKER"];
			unit = pPlayer:InitUnit (iUnitID, capital:GetX(), capital:GetY(), UNITAI_WORKER, DirectionTypes.DIRECTION_EAST);
			unit:JumpToNearestValidPlot();

			iUnitID = GameInfoTypes["UNIT_SWORDSMAN"];
			unit = pPlayer:InitUnit (iUnitID, capital:GetX(), capital:GetY(), UNITAI_DEFENSE, DirectionTypes.DIRECTION_EAST);
			unit:JumpToNearestValidPlot();

		-- Random map
		else
			local startPlot = pPlayer:GetStartingPlot();
			local capital = pPlayer:InitCity(startPlot:GetX(), startPlot:GetY());
			capital:SetPopulation(5, true);
			
			local iNumUnits = startPlot:GetNumUnits();
			local pUnit;
			for i = 0, iNumUnits do
				pUnit = startPlot:GetUnit(i);
				if (pUnit ~= nil and pUnit:IsFound()) then
					pUnit:Kill();
				end
			end
						
			local iBuildingID;
			iBuildingID = GameInfoTypes["BUILDING_PALACE"];
			capital:SetNumRealBuilding(iBuildingID, 1);
			iBuildingID = GameInfoTypes["BUILDING_MONUMENT"];
			capital:SetNumRealBuilding(iBuildingID, 1);
			iBuildingID = GameInfoTypes["BUILDING_SHRINE"];
			capital:SetNumRealBuilding(iBuildingID, 1);
			iBuildingID = GameInfoTypes["BUILDING_GRANARY"];
			capital:SetNumRealBuilding(iBuildingID, 1);
			iBuildingID = GameInfoTypes["BUILDING_WALLS"];
			capital:SetNumRealBuilding(iBuildingID, 1);
			if (pPlayer:GetCivilizationType() == GameInfoTypes["CIVILIZATION_RUSSIA"]) then
				iBuildingID = GameInfoTypes["BUILDING_KREPOST"];
			else
				iBuildingID = GameInfoTypes["BUILDING_BARRACKS"];
			end
			capital:SetNumRealBuilding(iBuildingID, 1);
			iBuildingID = GameInfoTypes["BUILDING_LIBRARY"];
			capital:SetNumRealBuilding(iBuildingID, 1);
			if (pPlayer:GetCivilizationType() == GameInfoTypes["CIVILIZATION_ARABIA"]) then
				iBuildingID = GameInfoTypes["BUILDING_BAZAAR"];
			else
				iBuildingID = GameInfoTypes["BUILDING_MARKET"];
			end
			capital:SetNumRealBuilding(iBuildingID, 1);
			if (pPlayer:GetCivilizationType() == GameInfoTypes["CIVILIZATION_SONGHAI"]) then
				iBuildingID = GameInfoTypes["BUILDING_PYRAMID"];
			end
			capital:SetNumRealBuilding(iBuildingID, 1);
		end
	end
end
Now, when I try to do something similar the different values don't get indexed or have 'nil' values. Obviously, I am still missing something.
  • Is it because I missed a list of already-defined functions that you can modify independently of Game Events?
  • Is it because Game Events located elsewhere in a Lua file call the elements of those functions?
  • Is it because you need to create a SQL file that will provide the link between a new function, its arguments and the game?

2-Game Events Straitjackets
If I keep using the shortcut I used to get my isOverseasLocation function to work, then I face another kind of problems: (a) how to convert arguments so that they can be read by the event, (b) what to do when you need to include one more argument.

Using SerialEventCityCreated in the Tutorial Lua for example:
Spoiler :
Code:
function OnCityCreated ( hexPos, playerID, cityID, cultureType, eraType, culture, population, size, fowState )
	
	-- make the first city a pop 2 city
	local pPlayer = GetPlayer();
	if (g_iNumCitiesFounded == 0) then
		for v in pPlayer:Cities() do
			v:SetPopulation(2, true);
		end
	end

	g_iNumCitiesFounded = g_iNumCitiesFounded + 1;
	UpdateCityDisplay();
end
Events.SerialEventCityCreated.Add( OnCityCreated );
Here the problems come from conversion:
  • how to move from a plot defined by X & Y to hexPos (a Vector3)
  • playerID doesn't seem to work in the standard iPlayer way (local pPlayer = Players[playerID]) doesn't return a value
  • How to connect functions that redefine different types of Events and thus have different types of arguments.
  • The Event doesn't refer to a unit, yet I need a pUnit in my function to (a) discriminate between colonists & settlers, and (b) to identify which city that has just been founded.
I have tried different approaches to bestow bonuses to new overseas cities but so far I have been unable to get all the arguments I need to work together.

So basically it's the issue of callers, functions and args all over again. I believe I understand how it works, but I feel I'm still missing a crucial element to make it work. :think:
 
I had troubles understanding this post, so bear with me if I am slightly off-topic. ;)

Functions without callers
Use notepad++ to search for all references: the AddInitialUnits function is actually called at the end the LUA file. That is, as soon as the mod is loaded, the scenario checks whether it was already initialized. If it was not yet, then AddInitialUnits and others are called right away.

Not that all functions need to be called by a GameEvent. Here are the possible sources:
* Called directly at the main scope of a lua file (invoked the first time a file is read).
* Called by an UI control (see Button:RegisterCallback, ContextPtr:SetUpdate, etc)
* Called by a GameEvent, a LuaEvent or an Event

And of course any code ran after one of those conditions can in turn call another function. Typically, only 5% to 10% of your code is directly hooked to an event or something else, the rest of your code is invoked by those "roots".

On a side note, "includes" allow some files to use the content defined in another file, so sometimes you will see functions that do not seem to have been defined and you will need to search for them in other files.


Game Events Straitjackets
* Converting hexpos to grid coords: see post #12 (bottom)
* Plumbing problem: see also post #12: event -> interface function -> real function. The interface functions take the playerID, hexPos and such and use them to infere the pPlayer, pPlot and such, then call the real function with those objects. That way you can have one clean function with the arguments of your choice, and have it used by multiple events through one interface function per event.
* Dealing with the fact that the unit is dead when SerialEventCityCreated fires: see post #14, point 6. You need a two-steps algorithm with two different events. Store and update positions on SetXY and others, test location on SerialEventCityCreated.
* PlayerID *is* your typical iPlayer and you can use it with Players[playerID].

So, yeah, it seems like you're not comfortable with functions yet. But functions in programming are like functions in mathematics: if you have a f(x, y, z) and x, y and z are all defined from a "t" variable with x(t), y(t) and z(t), then you must write:
f(x(t), y(t), z(t)).

This is the same as
Code:
function test(t)
   local x = x(t)
   local y = y(t)
   local z = z(t)
   return f(x, y, z)
end

Now, maybe that does not help. :D
But anyway it looks like you're at a point where you do not need more explanations on functions and args, and you're rather at that suspended moment where you're suddenly going to figure it out properly while trying to solve a practical problem.
 
No, it does help a lot! The problem is I was further behind than both of us thought at the beginning. As a result, now I better understand some of your earlier posts than at the time. A lot of my assumptions/what I thought I knew at the time was wrong, now that, with your guidance, I have unlearned what I had learned it's easier to apply those teachings (we need a 'Yoda' emote ;) )

Yes, I think I'm pretty much set with functions since you confirmed what I suspected: they are all connected to events somewhere in the file. I just need to hone my skills at finding them.

And of course any code ran after one of those conditions can in turn call another function. Typically, only 5% to 10% of your code is directly hooked to an event or something else, the rest of your code is invoked by those "roots".
That's likely to be my next challenge, to better understand functions and scopes. The explanation of scopes you gave a few posts ago is rather clear, now it's mostly a question of practice.

One quick question (hmm, not so quick retrospectively): given the way scopes & roots work, is there a recommended/necessary order to functions. For instance, is it better to go from the most specific to the most general? That would go against usual "narrative" order, but could make sense if later--more inclusive--functions call upon earlier--more specific--functions. Or maybe order isn't even important (if functions are treated independently and contexts/roots are built once the whole code is read)... my ignorance of basic coding logic shows here... got to get a "Programming for Dummies" book (there are none on Lua, I checked...) :crazyeye:

Converting hexpos to grid coords: see post #12 (bottom)
From Post #12
-- Hexpos to plot
local gridPosX, gridPosY = ToGridFromHex( hexPos.x, hexPos.y );
local plot = Map.GetPlot( gridPosX, gridPosY );
Yes, I considered those. Yet, the SerialEventCityCreated calls for hexpos while I have plot coordinates. I felt like I needed to do the opposite conversion. I still have to learn how to better use "interface functions". Practice will hopefully help me figure out and remember all the available functions/objects.

PlayerID *is* your typical iPlayer and you can use it with Players[playerID]
Thought that didn't work. Then again, it might have come from other mistakes. If I remember well, my args ordering might have been off when I tested it.

Dealing with the fact that the unit is dead when SerialEventCityCreated fires: see post #14, point 6. You need a two-steps algorithm with two different events. Store and update positions on SetXY and others, test location on SerialEventCityCreated.
and
post #14, point 6.2 & 6.3
* 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.
I guess I could use the function I had created to prevent Colonist to found on their home continent for this purpose. Now, when you say "store the colonist position on file-level variable" do you mean just embedding a
Code:
if not IsOverseasColonist(iPlayer, iUnitID, iX, iY) then
condition in my onOSCityCreatedByColonist function or something else altogether?

Now, maybe that does not help.
Not at all! I knew this last post might have been a bit confusing and almost wrote it was. I am yet to become proficient in the coding language/terms and sometimes I prefer to use vernacular to avoid using programming lingo inappropriately (not to mention English is my second language).

Now, I truly hope that, in the future, you will edit/reorganize your posts in this thread into a tutorial (I will be honored if you use my final code as an example). In the meantime, I personally plan to re-label this thread along the lines of "My First Lua File: A Walkthrough with DonQuiche" once I'm done :hatsoff:
 
I am glad to see you're connecting the dots. :)

Functions ordering
Regarding functions ordering, as long as they're not locals it does not matter: this is because the functions themselves will be executed after the whole file has been read and all global variables assigned. However if you make your functions locals (by adding the "local" keyword before "function"), remember that at any point of the code only the locals defined before are visible.

Right now I advise you to make all your functions globals unless they have very generic names that could conflict with other included files ("Update" is a typical example), but it should be pretty rare. So now that you're free to order tham as you want, do it in a way that will increase readability: typically you have important functions that are divided into subfunctions. Put the related subfunctions after every function (they form a hierarchy, so think of how the folders are laid out in the windows' explorer's left panel). Also you will often have little helper functions that are used by many functions, so gather those helpers either at the top or the bottom.

Hexpos to plot
No, it's the correct way: you do not want to invoke SerialEventCityCreated, you want to register it. And it will provide you a hexpos that you need to convert to a plot. Now if you ever need the opposite thing, then use hexPos = HexToWorld(Vector2(plot:GetX(), plot:GetY()))

Two-steps algorithm
What I mean is to declare a local table at the top of your file. The keys will be units, the values will be their positions (stored in a Vector2).

Beginning of the file: local colonists = {}
NewTurn event: scan this table and set nil for the dead colonists (it removes them from the table).
SetXY event: colonists[unit] = Vector2(plot:GetX(), plot:GetY())
CityCreated event: enumerate all colonists and check that one of them match the city coords. The test IsOverseas.

Tutorial
I actually hate tutorials. ;)
I hate writing them and I hate reading most of them: I almost always think they're far too long and verbose when I just need a few examples, maybe a couple of one-line bullet points, and more importantly a reference documentation. However if you want to do something with our discussion, feel free to do so. Also I do not think there are many people like you who are ready to jump naked at programming, with just a knife in their tooths, a tutorial in one hand and a keyboard in the other one.

On my side I will rather focus on the little bot I wrote to automatically generate a complete, up-to-date and rich API reference on the wiki. Something similar to the 2kgames wiki but far better, richer and comprehensible, that will be useful both for beginners and experienced modders. :)
 
Thank you again Don. As usual, very useful.

I have to admit I don't really how to implement the "Two-steps algorithm" that you recommended using. I have a general idea of the purpose of lists, however, I don't quite know how to use them in a code and the examples in CIV5 files are scant and they rarely concern units. The closest use I have been able to find concerns the purchase of Great People with Faith (a file which incidentally I was trying to find for other purposes). I tried to design something based on that example, but being conscious I didn't know what I was doing, I kinda left it at that.

Below are the elements of the code I have been building so far. I know that functions #2 and #3 work well, but I am still looking for ways to connect #4 with the rest. Your help would, once again, be greatly appreciated.

Code:
--#1
local colonists = {}
function possibleOSColonists(Type, Condition)
	local unit = GameInfo.Units[Type]
	if(unit ~= nil) then
		colonists[unit] = Vector2(plot:GetX(), plot:GetY())
		possibleOSColonists[unit.ID] = function(isOverseasColonist)
			if(Condition()) then
				return true
			end
		end
	end	
end
Events.ActivePlayerTurnStart.Add(possibleOSColonists)

--#2
function isOverseasLocation(iPlayer, iUnitID, iX, iY)
	local pPlayer = Players[iPlayer]
	local capitalPlot = pPlayer:GetStartingPlot()

	local pUnit = pPlayer:GetUnitByID(iUnitID)
		
	if (capitalPlot:GetArea() ~= pUnit:GetPlot():GetArea()) == true then
		print ("Overseas Location", capitalPlot:GetArea(), pUnit:GetPlot():GetArea())
		return true
	else
		print ("Not an Overseas Location")
	end
end

--#3
function isOverseasColonist(iPlayer, iUnitID, iX, iY)
		
	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()
			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)

--#4
function onOSCityCreatedByColonist(hexPos, playerID, cityID, cultureType, eraType, continent, populationSize, size, fowState)
	
	local pPlayer = Players[iPlayer]
	local pUnit = pPlayer:GetUnitByID(iUnitID)
	print("entry point ", iPlayer, iUnitID)
	
	if pUnit:GetUnitType() == GameInfoTypes["UNIT_COLONIST"] then
		print("Unit Type",pUnit:GetUnitType(), "coords", pUnit:GetX(), pUnit:GetY())
		
		--if (pUnit:GetOwner() > 20) then
			--print("City-States shouldn't have Colonists")
			--return false
		--end
			
			if not (isOverseasColonist()) then
				print("Not an overseas location, no goodies for you!")
				return false
			end
			
			if pUnit == (pUnit:IsFound() and pUnit:IsOverseasColonist()) then
				print("Founding a new overseas colony")	
			local pPlot = pUnit:GetPlot()
			local gridPosX, gridPosY = ToGridFromHex( hexPos.x, hexPos.y );
			local newOSCityPlot = Map.GetPlot( gridPosX, gridPosY ); 
			local newOSCity = newOSCityPlot:atPlot()
			
			print("newOSCity Founded",newOSCity:getID(), "coords", newOSCityPlot:atPlot())			
				
				for newOSCity in pPlayer:Cities() do
				newOSCity:SetPopulation(3, true)
			
				local iBuildingID
		
					if (pPlayer:GetCivilizationType() == GameInfoTypes["CIVILIZATION_ETHIOPIA"]) then
						iBuildingID = GameInfoTypes["BUILDING_STELE"];
					else
						iBuildingID = GameInfoTypes["BUILDING_MONUMENT"]
					end
					newOSCity:SetNumRealBuilding(iBuildingID, 1)
		
					if (pPlayer:GetCivilizationType() == GameInfoTypes["CIVILIZATION_MAYA"]) then
						iBuildingID = GameInfoTypes["BUILDING_MAYA_PYRAMID"];
					else
						iBuildingID = GameInfoTypes["BUILDING_SHRINE"]
					end
					newOSCity:SetNumRealBuilding(iBuildingID, 1)
		
					if (pPlayer:GetCivilizationType() == GameInfoTypes["CIVILIZATION_CHINA"]) then
						iBuildingID = GameInfoTypes["BUILDING_PAPER_MAKER"];
					else
						iBuildingID = GameInfoTypes["BUILDING_LIBRARY"]
					end
					newOSCity:SetNumRealBuilding(iBuildingID, 1)
					print("Founding inland colony")	
				
						--Is city coastal or inland?
					if pCity:IsCoastal() == true then
						iBuildingID = GameInfoTypes["BUILDING_LIGHTHOUSE"]
						newOSCity:SetNumRealBuilding(iBuildingID, 1)
						print("Founding coastal colony")
				end
			end
		end
	end
end
GameEvents.SerialEventCityCreated.add(onOSCityCreatedByColonist)
Tutorials
I hear you!
I wouldn't dare edit the elements you presented in this thread, since I'm not yet all that comfortable with many aspects of it. However, as mentioned above, I'll try to find a way to point toward the wealth of knowledge and time you've invested in helping me.
 
Hello. :)

Turn start
* possibleOSColonists expects arguments that ActivePlayerTurnStart will not provide.
* First clear the colonists table (assign a new table to it)
* Then scan all of the players units (search for "GetNumUnits") and store all the colonists' positions.
Pseudo-code:
Code:
colonists = {}
for all player's units do
   if unit is a colonist then
      store colonist position
   end
end


SetXY
* You're misunderstanding pUnit:CanFound, the way you use it is useless (it does nothing, it just returns true if the unit is on a hex where it can found a city : it's a getter, not a setter). So you could just remove it.
* Anyway remember that you cannot prevent colonists to found new cities, so why test that? You do not need most of that stuff.
* Instead just check whether the unit is a colonist, then store its new position in the colonists tables. This is much simpler than what you did.
Pseudo-code:
Code:
if unit is a colonist then
   store colonist position
end

SerialEventCityCreated
* The overall algorithm is good
* You cannot just call isOverseasColonist like that: it expects argument and it is not suited to what you want to do anyway.
* Same goes with pUnit:IsOverseasColonist, pUnit has no such function.
* Instead replace those two parts with the following: first get the city's plot, then scan all colonists and see if there was one of them on this plot. If not, then it was not founded by a colonist, so return. After that point you know it was founded by a colonist, so you're just left with checking that this plot is overseas.
* Then you correctly retrieve the plot. But the city line is wrong. You have been provided the city ID as an argument, so use it with Game.GetCityByID(id)
* After that there is a "for" loop that does not make sense. Why scan all cities?
* The rest is fine, although there is a more generic way to get civ-specific buildings. But at this point it would only make you pull hairs out of your head so let's leave that for later. ;)

Some Lua to help you:
Code:
function WasFoundedByAColonist(city)
   for unit, plot in pairs(colonists) do
      if (unitPlot == city:Plot()) then return true end
   end
end
 
Hi again! Not on vacation anymore, so I don't have as much time to devote on this project :(

2 quick questions before I get back to working on the code with your new suggestions:

1- Info Storage
I'm not sure how to "store colonist position". Is this done simply by defining a new category, something along the line of unitPosition = unit:getPlot()? Or do you mean another system for storing data (a system I might not be aware of).

2- Scanning for cities founded by colonists

Instead replace those two parts with the following: first get the city's plot, then scan all colonists and see if there was one of them on this plot. If not, then it was not founded by a colonist, so return. After that point you know it was founded by a colonist, so you're just left with checking that this plot is overseas.
I was just wondering about a possible problem with this approach: what if the colonist unit move 1 hex before founding the city, it won't be on the hex where it was at the beginning of the term and hence wouldn't register, right?

Edit One more question!
3- ActivePlayerTurnStart
The list of events on the Wiki doesn't indicate the arguments for ActivePlayerTurnStart, do you know where I can find it? Also, now that I think about it, wouldn't it be more appropriate to use PlayerDoTurn instead since it applies to both human player and AIs?
 
Hello :)

1. I mean a table where keys are units and values their positions. This:
colonists[unit] = Vector2(plot:GetX(), plot:GetY())

2. If the colonist moves 1 hex before founding the city, SetXY will trigger and you will update the colonist's position, so no problem.

3. Regarding ActivePlayerTurnStart, it has no argument afaik. Now, I thought it was triggered for both human and AI but if you're correct then indeed you need to use PlayerDoTurn, provided it starts at the beginning of the turn player's turn.
 
Hi again!

There are many things you suggested to do that are truly at the limit of what I have mastered so far (which, admittedly, isn't much). I'm pretty sure that what I've tried so far doesn't make much sense. Would you mind giving it a look? :help:

Spoiler :
Code:
local colonists = {}
function possibleOSColonists(iPlayer)
	for iPlayer = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
		local pPlayer = Players[iPlayer];
		local iTotalUnits = pPlayer:GetNumUnits()
			
			for i = 0, iNumUnits do 
			if(iNumUnits ~= nil) then
				pUnit = pPlayer:GetUnit(i)
				
				if (pUnit:GetUnitType() == GameInfoTypes["UNIT_COLONIST"]) then
				colonist[unit] = Vector2(plot:GetX(), plot:GetY())
				end
			end
		end
	end	
end
GameEvents.PlayerDoTurn.Add(possibleOSColonists)

function isOverseasLocation(iPlayer, iUnitID, iX, iY)
	local pPlayer = Players[iPlayer]
	local capitalPlot = pPlayer:GetStartingPlot()

	local pUnit = pPlayer:GetUnitByID(iUnitID)
		
	if (capitalPlot:GetArea() ~= pUnit:GetPlot():GetArea()) == true then
		print ("Overseas Location", capitalPlot:GetArea(), pUnit:GetPlot():GetArea())
		return true
	else
		print ("Not an Overseas Location")
	end
end
GameEvents.UnitSetXY.Add(isOverseasLocation)

function WasFoundedByAColonist(hexPos, playerID, cityID, cultureType, eraType, continent, populationSize, size, fowState)
	
	local pPlayer = Players[iPlayer]
	local pUnit = pPlayer:GetUnitByID(iUnitID)
	print("entry point ", iPlayer, iUnitID)
	
	if pUnit:GetUnitType() == GameInfoTypes["UNIT_COLONIST"] then
		print("Unit Type",pUnit:GetUnitType(), "coords", pUnit:GetX(), pUnit:GetY())
		
		if (pUnit:GetOwner() > 20) then
			print("City-States shouldn't have Colonists")
			return false
		end

			local pPlot = pUnit:GetPlot()
			for pUnit, pPlot in pairs(colonist) do
			if (unitPlot == city:Plot()) then 
									
				if unitPlot not (isOverseasLocation(iPlayer, iUnitID, iX, iY)) then
				print("Not an overseas city")
				return false
				end
				
				if (isOverseasLocation(iPlayer, iUnitID, iX, iY)) then
				print("newOSCity Founded",newOSCity:getID(), "coords", newOSCityPlot:atPlot())
				
				local newOSCity = unitPlot:GetPlot()
				for newOSCity in pPlayer:Cities() do
				newOSCity:SetPopulation(3, true)
			
				local iBuildingID
		
					if (pPlayer:GetCivilizationType() == GameInfoTypes["CIVILIZATION_ETHIOPIA"]) then
						iBuildingID = GameInfoTypes["BUILDING_STELE"];
					else
						iBuildingID = GameInfoTypes["BUILDING_MONUMENT"]
					end
					newOSCity:SetNumRealBuilding(iBuildingID, 1)
		
					if (pPlayer:GetCivilizationType() == GameInfoTypes["CIVILIZATION_MAYA"]) then
						iBuildingID = GameInfoTypes["BUILDING_MAYA_PYRAMID"];
					else
						iBuildingID = GameInfoTypes["BUILDING_SHRINE"]
					end
					newOSCity:SetNumRealBuilding(iBuildingID, 1)
		
					if (pPlayer:GetCivilizationType() == GameInfoTypes["CIVILIZATION_CHINA"]) then
						iBuildingID = GameInfoTypes["BUILDING_PAPER_MAKER"];
					else
						iBuildingID = GameInfoTypes["BUILDING_LIBRARY"]
					end
					newOSCity:SetNumRealBuilding(iBuildingID, 1)
					print("Founding inland colony")	
				
							--Is city coastal or inland?
						if pCity:IsCoastal() == true then
							iBuildingID = GameInfoTypes["BUILDING_LIGHTHOUSE"]
							newOSCity:SetNumRealBuilding(iBuildingID, 1)
							print("Founding coastal colony")
						end
					end
				end
			end
		end
	end
end
GameEvents.SerialEventCityCreated.add(WasFoundedByAColonist)

Here are the main things I'm not sure about:
  1. Should I create 2 separate functions: 1 to check for city creation/colonist/overseas location, and 1 to add all the goodies?
  2. How to connect the "possibleOSColonists" function and the "WasFoundedByAColonist" function, i.e. how to use the stored info.
  3. How to cycle through all units and then check for colonists. pPlayer:GetNumUnits returns a integer, but I need then to verify the unitID. I have checked different examples of pPlayer:GetNumUnits() and none of them does something near what I'm trying to do.
  4. This is the first time that I try to use in pairs. Again, there are only few useful examples among the game Lua files.
  5. When and how to check for "isOverseasLocation", I'm not sure the way I use it right now would work.
I never thought this would be so difficult to do... and I thought the colonist unit would have been the easiest part of what I want to implement in my mod :cringe:
 
Hello.

One problem you have since the beginning: you start too big. You wrote plenty of functions and none of them work and you can't understand why because it's more than you can eat. Stop writing a lot of code. Think about one small piece of code you will need. What do you want it exactly to do (which args should it get, what should it do, should it return something?)? Then write that small piece of code. Then a little test to ensure it does what you want. As long as the test does not pass, work on that piece of code. It's only once it works that you should move to the next piece of code.

PlayerDoTurn
* The general algorithm is almost correct.
* You need to reset the colonists position at the beginning: colonists = {}
* Why request an iPlayer if you do not use it and instead scan all players? Even if PlayerDoTurn provide an iPlayer, if you do not need it, do not declare it, it will just be ignored. Now what do you really want? To store the colonists for all players (then remove the parameter) or to store the colonists for just the current player (then remove the players loop)? The latter makes more sense.
* You declared an iTotalUnits and use an iNumUnits, fix that. Then remove the iNumUnits test, iTotalUnits will never be nil.
* You start at 0, so stop at iTotalUnits - 1. (from 0 to 9 there are ten numbers).
* You should ignore dead units, test unit:IsDead(). Yes, sometimes dead units are still enumerated, I do not know exactly when civ5 cleans them up.
* You store plot:GetUnitX() and plot:GetUnitY(). But there is no plot! Use unit:GetX() and unit:GetY()
* This is a bad function name. What does it do? It stores the colonists position. So why not "StoreColonistsPositions" ? Or "OnNewTurn"?


UnitSetXY
* That one is correct. BUT it's not what you're supposed to do on SetXY. You're supposed to update the colonist's position. Yes, you should write an UpdateColonistPosition (or OnUnitMove) function, not an IsOverseasLocation. Besides if you return true on SetXY you will prevent the unit move!


Restart small
I won't correct SerialEventCityCreated, it's also full of errors. It's not that I am angry or tired, it's just that I do not do you a favor, you will never learn that way and the proof is that you're unable to read the FireTuner output, understand your errors (even basic ones you should easily be able to figure out by yourself), and hence keep repeating them. Start small. Five lines of code that work. Then seven lines of code that work. Then nine lines of code that work. Etcetera. Not fifty lines of code and, oh, well, it does not work and there are plenty of errors, why? ;)

For now, just put everything in a comment block, or remove them and make a backup somewhere. Then start again with PlayerDoTurn. Here is a code will need to check that everything is correct after PlayerDoTurn/StoreColonistsPositions/OnNewTurn (whatever you name it) has been invoked. Ensure that what you read is what you expect.
Code:
function PrintColonistsPositions()
   for pUnit, vPosition in pairs(colonists) do
       print(pUnit:GetUnitType() == GameInfoTypes["UNIT_COLONIST"], vPosition .x, vPosition .y)
   end
end

And, yeah, I know: it's frustrating, it's painful, it's castrating. Stupid computer! And now DonQuiche tells you to start over! Yep. :D

We all went through that despair at the beginning. But trust me, it's for your own good, you're on a bad track. While your algorithmic skills have improved, on the coding side you didn't progress in your recent posts. This is something you will only learn by managing to make small pieces of code work, one after the other.


Your questions
1. Yes, such a split would be a very good idea.
2. In WasFoundedByAColonist, scan all positions in the "colonists" table (see PrintColonistsPosition), and if one match the city position (test x and y), then it was founded by a colonist.
3. You already know. Actually this is about the only part you did correctly in possibleOSColonists: pUnit:GetUnitType() == GameInfoTypes["UNIT_COLONIST"]
4. Look at my PrintColonistsPosition()
5. You guessed right. You should call it on "SerialEventCityCreated". First check whether the city was founded by a colonist, then check whether the city plot is overseas. But I already said that two times before. ;) Again it's because you have more than you can eat.
 
Back
Top Bottom