Modding Guides

Scripting in Populous 1.5: Lua Tutorial Continued

Written by: Unknown_TAS

Index

A little story behind this tutorial

I am not a programmer, nor am I experienced with the game’s engine in any way, so what you will read below is my own way of understanding the different concepts presented in this tutorial, after having asked a multitude of questions to other people, mainly MrKosjak, and also Leo for making me understand why some things do what they do.

With that being said, you should probably take my explanation of different concepts with a grain of salt. However, independently of me knowing or not knowing strictly correct definitions for different game and programming concepts, the main goal here is to try and help you understand what you are actually seeing, and to possibly spark your interest in AI scripting and maybe give you a head start.

This attempt at clarifying some concepts related to Lua and the game, come as an attempt to patch some of the blank knowledge and grey areas left over by the amazing 410172_Chief’s tutorial. It will not, in any way, give you everything you need or might want to know about it. However, I am hoping that both 410172_Chief’s tutorial and this one, may give you a tremendous head start into what these Lua chunks are all about.

In here we will touch the concepts presented in 410172_Chief’s Lua tutorial and try to give them some more contextualization.

Where does all this information come from? How can I have any idea of what is available for me to use? How to use it? What are these Map Elements and Coord3d and 2D?

Yes, the idea is to explain you a bit more, but at the same time, it is to try and give you the tools for you to search, experiment and discover by yourself in an easier way.

Where to start?

The first tool you need to get acquainted to is the Populous Script 3 Documentation:

http://www.populous3.info/script3_doc/

In here you pretty much have everything you might ever need. Well… not really everything, but the big majority.

Let’s get started

Do you remember the first thing that 410172_Chief told you that gets executed in a script?

The Modules!

These pretty much hold a ton of code that makes your life much easier. Why? Because every function you use has been created previously in some way or another by someone else, which you can now use in order to speed up your scripting, instead of coding all of that yourself.

How do you know to which Module a certain function you might be using belongs to?

Thankfully that’s documented. Let’s take a look again at the PopScript3 Doc:

http://www.populous3.info/script3_doc/globals_func_a.html

In here you can see the different Modules you have at your disposal. If you click any of them, you will notice that a bunch of functions and definitions seem to appear.

What a mess!

How to use PopScript3 Documentation

To try and explain to you what you are seeing in the website, let’s take one of the first things you use in 410172_Chief’s tutorial as an example:

computer_init_player(_gsi.Players[TRIBE_ORANGE])

If you go into the Module_Players.h and you pay close attention to it, you might notice that inside of it there is this function called computer_init_player(struct player*). This function will make the AI actually execute the wonderful and amazing lines of code you have scripted for them. In that sense, you will have to actually know what the functions do, or test them in some way, because there is nothing in that website specifically telling you what their behaviour is.

Now you might be wondering, but what is that struct Player*?

That is giving you a clue to what the function accepts as an argument. Of course, you have no clue of what in the world is struct Player* yet, but that is something we will try to amend.

It is not always very easy to understand to which module something belongs too, which means that dealing with AI scripting and trial and error will eventually make you instinctively aware of which module something might belong too.

In order to use this function, we need to call the Module_Players and the Module_Globals.

The Module_Players is called, because the function is defined inside that Module, as we have already seen. The Module_Globals contains the GlobalSaveItems, which is what you need in order to access the GlobalSaveItems Players attribute.

It goes like this: _gsi.Players[TRIBE_ORANGE], so we end up with:

computer_init_player(_gsi.Players[TRIBE_ORANGE])

If you travel to the Module_Globals, http://www.populous3.info/script3_doc/_module___globals_8h.html, you will find that the gsi is contained inside it.

We use it like “_gsi”, because that is the way it is defined in the UtilRefs.lua you are using, however you can also use it like gsi(), since gsi is a function.

If you open it, http://www.populous3.info/script3_doc/struct_global_save_items.html, you will end up in the GlobalSaveItems structure reference. Inside it you will be able to spot the one we just used “Players”. You can read on the left of the blue Players word that it accepts an array, which you open using square brackets “[]”.

We end up with _gsi.Players[]

The Player structure the array is after is either a numeric value corresponding to the tribe number, or the respective tribe identifier which is defined inside the Module_Defines. It goes like this:

TRIBE_BLUE or 0, TRIBE_RED or 1, TRIBE_YELLOW or 2, TRIBE_GREEN or 3, TRIBE_CYAN or 4, TRIBE_PINK or 5, TRIBE_BLACK or 6 and TRIBE_ORANGE or 7

Note that if you use TRIBE_COLORHERE* you will have to import the Module_Defines.

We end up with _gsi.Players[TRIBE_PINK] or _gsi.Players[5] for example.

I have a little test for you now! How would you go on about finding the corresponding tribe number for the Red Tribe?

_gsi.Players[TRIBE_RED].PlayerNum or _gsi.Players[1].PlayerNum

PlayerNum returns the number of a tribe. The only way for you to know that, is to actually know it, because as I have stated previously, there is no place, at least yet, where you can see what each class member or function does.

The output of this result will be 1.

This little trial takes us to the next step. How can we test what all these different attributes do?

How to find out what certain commands return

The easy way to find things out is to give the value of that which you want to discover to a variable and log the value of that variable to the debug output.The debug output can be accessed by pressing the shift + f1 keys while playing your map.

Here we have an example of how you can log out the result of a variable:

Example 1:

if (everyPow (24, 1)) then
local variable = _gsi.Players[TRIBE_BLUE].PlayerNum
log (string.format((“This is a very handy tool: %d”, variable))
end

Every second in the game are 12 turns. This code will print the value of our variable to the debug output every 2 seconds.

You can place a variable in the middle of text by using %d:

log (string.format(“This is variable one %d, this is second %d and third %d”, variable1, variable2, variable3))

If you use this example above, make sure to place import(Module_String) in the top of your Lua file, otherwise it will give you an error when trying to make the string.

If you find that way too complicated, you can simply use:

Example 2:

if (everyPow (24, 1)) then
local variable = _gsi.Players[TRIBE_BLUE].PlayerNum
log (“Any text can go here: ” .. variable)
end

This way, you don’t need to use import(Module_String).

The “..” is the concatenation operator, which is necessary for joining two strings. This way Lua will convert your variable into a string.

Use log(“Wow look “ .. variable1 .. ” What a great thing “ .. variable2 .. ” and this one “ .. variable3) to print several variables in the same log.

The log() function can only print out strings. This means that if you try to log out your variable directly into the log() function, you will incur in an error. In case you wish to only log a variable without any text, you have to first convert it into a string, either with string.format() or tostring():

Example 3:

if (everyPow (24, 1)) then
local variable = tostring(_gsi.Players[TRIBE_BLUE].PlayerNum)
log (variable)
end

This way the variable will be stored as a string and not as a number. If you want to use it elsewhere, even though Lua might convert it back to a number if need be, you may want to pay attention to that detail and do log( tostring(variable) ) instead, just in case.

But can we test everything with variables?

As long as whatever you are wanting to store in a variable returns back something which can actually be stored, yes!

However, of course that things are always a little bit more complicated than that. There are several structure types that we need to get familiar with, in order for us to use certain functions, or for us to be able to retrieve certain data. That in itself goes a very long way.

We will look at those which were already discussed in 410172_Chief’s Lua tutorial.

Hey Wait! What are these everyPow(24, 1) we are using? What in the world is that?

That is a function that is defined inside the “UtilRefs.lua” file that 410172_Chief shared in his tutorial.

But what are these files?

What are the UtilPThings.lua and the UtilRefs.lua files?

The UtilRefs.lua and the UtilPThings.lua are custom made files which help you in your AI scripting with a series of functions that you can use, which will make your scripting easier.

These are files anyone can make. You eventually might also be making them in the future. Their goal might be diverse. However, it is usually related with the automatization of certain processes or simply to make coding easier and faster.

For example, the UtilPThings.lua makes it easier and less confusing to assign spells and buildings to a tribe and even to give spell shots to those same tribes. For that is defines three functions:

PThing.SpellSet(player, spell, input, charge*)
“player” is the tribe you are attributing the spell to.
spell” is the spell you are enabling for that tribe.
“input” is if the spell can be or not used by that tribe.
“charge” is if the spell starts the game being charged of not. * For the AI it doesn’t really matter if the spell is or isn’t charging, since they use global mana.

Example: PThing.SpellSet(TRIBE_GREEN, M_SPELL_LAND_BRIDGE, TRUE, TRUE)
This will give the Landbridge spell to the green tribe and it will start with the landbridge spell not charging.

PThing.BldgSet(player, building, input)
“player” is the tribe you are attributing the spell to.
“building” is the building you are enabling for that tribe.
“input” is if the building can be or not used by that tribe.

Example: PThing.BldgSet(TRIBE_ORANGE, M_BUILDING_TEMPLE, TRUE)
This will give the Temple building to the orange tribe.

PThing.GiveShot(player, spell, amount)
“player” is the tribe you are changing the spell shots to.
“spell” is the spell you are changing the number of starting shots to.
“amount” is the number of shots you are giving to that spell.

Example: PThing.GiveShot(TRIBE_CYAN, M_SPELL_CONVERT_WILD, 3)
This will give 3 shots to the convert spell.

The UtilRefs.lua defines some functions which can make your scripting easier and less confusing:

GetTurn()
Returns the current game turn.

GetPlayerPeople(pn)
Returns the current population for the tribe specified in “pn”.

randSign()
Returns either a -1 or 1 value.

everyPow(a, b)
Returns true at every a^b game turns.

every2Pow(a)
Return true at every a^2 game turns.

DoesExist(table, input)
Checks if input exists inside the table.

GetPopLeader()
Returns the number corresponding to the tribe with the highest population count.

Tablelenght(table)
Returns the length of a table.

The Module_Map

Let’s take a look at the basic elements of the Map Module.

Here: http://www.populous3.info/script3_doc/_module___map_8h.html is where you can find the Module we will be discussing.

In 410172_Chief’s tutorial you were presented to several different functions which are contained inside this module. I will try to present to you what those functions are all about, in a way that might make you understand why they are used in the way they are.

X and Z positions

The first function you were presented to was MAP_XZ_2_WORLD_XYZ(x, z).

This function is useful for when you only have the X and Z positions, yes, the ones you see in the World Editor. What it does is to output a structure Coord3D out of that X and Z values. This structure Coord3D can then be used in various Module_Map functions, which require the Coord3D or Coord2D structure as its arguments.

The function createThing(), which can be seen here: http://www.populous3.info/script3_doc/_ … 021c698ce3 requires a struct Coord3D as its 4th argument. The basic way to do this when you know the X and Z values of where you want to create such thing, is by using the MAP_XZ_2_WORLD_XYZ(x, z) function.

If you are confused about what these Coord3D and Coord2D are, let’s just say that the game engine does not understand our language. As such these Coordinates store the Xpos, Zpos and Ypos values, which some functions need. Xpos and Zpos are stored in the Coord2D, while Xpos, Zpos and Ypos are stored in the Coord3D structure. Yes! Y is the height, not Z.

You can see their structure here:

http://www.populous3.info/script3_doc/s … rd2_d.html
http://www.populous3.info/script3_doc/s … rd3_d.html

This is how you can use this function:

local c3d = MAP_XZ_2_WORLD_XYZ(128, 176) -- outputs the 128, 176 struct Coord3D to the variable c3d

Coord2D and Coord3D

The second and third function you are presented to in 140137_Chief’s Lua tutorial are world_coord(3d/2d)_to_map_idx().

These functions converts the structure Coord3D or 2D into a map index. The map index is used by some functions as their necessary arguments, like the SearchMapCells() function http://www.populous3.info/script3_doc/_ … fd8f2556a for example.

The map index is like a pointer, or an identity to the actual X and Z of the map cell as the game understands it.

When you convert world coordinates into the map index, you aren’t really converting those coordinates into something the game uses yet. The game uses both the map index and the X and Z values of a map cell.

This conversion happens on the backstage, we do not really see it happening. The game takes the map index and the actual X and Z value and makes a map location out of it. There is one structure that holds both the map index and the X and Z values and we shall talk about it in just a little bit.

Whenever you call the world_coord3d_to_map_idx() or world_coord2d_to_map_idx() functions, it is to convert Coord3D or Coord2D to a map index. Which means that you have a 2D coordinate or a 3D one and you need to convert them, in order to use them in a function for example.

Map Element

Contains almost everything about a map cell, like objects, land height, flags. These however are not map elements in themselves, they are just contained inside the MapElement. Your job is to know how to retrieve that data.

For example, you have in the SearchMapCells() function http://www.populous3.info/script3_doc/_ … 1fd8f2556a a last argument that is a function with the map element structure.

The “me” you see in 410137_Chief’s Lua tutorial:

SearchMapCells(CIRCULAR, 0, 0, 6, world_coord3d_to_map_index(c3d), function(me)

This means you can use anything that a given map cell contains inside its map element. You can check if the cell is water, or land. You can check if a brave is on that given cell at a certain moment in time. You can check how many trees are around the coordinates you specify. In resume, the map element contains the data of a cell, which can be used in your coding to make the AI react in a certain way to something that is actually happening in game. Pretty neat uh?

We will focus a bit more on this subject further down the line.

The main conversion from map element you see happening in 410137_Chief’s Lua tutorial is using the function map_ptr_to_world_coord2d(map element, coord2d). This function is making a struct Coord2D from the Map Element, so that it can be used with other functions. The objective of that conversion in the Lua tutorial is to use that map cell for the shaman to cast a landbridge, in order to save herself from drowning.

Why don’t we simply use the Map Element?

If you look deep inside, you can answer that question.

The createThing() function, used for the shaman to cast her landbridge, needs a Coord3D in order for it to work and not spout us with an error. We simply can’t make it accept what the function does not want to accept. Just like when you have way too many drinks. You can’t force your liver to accept that which he does not want to receive. As such, if we follow 410137_Chief’s Lua tutorial, we still need to convert it from Coord2D to Coord3D, so that it can be used in the createThing() function.

That is done using the function coord2d_to_coord3d(Coord2D, Coord3D), like you see in Chief’s tutorial.

Now you might be asking, but why don’t you immediately convert it into a Coord3D, from Map Element? Why are you going to all that trouble?

Because there is no function to do so. The reason why that happens… I don’t know it and as such you will have to accept it, until one of us can find that out.

The code will look like this:

local c2d = Coord2D.new()
map_ptr_to_world_coord2d(me, c2d)
local c3d = Coord3D.new()
coord2d_to_coord3d(c2d, c3d)

Now we can use the c3d coordinate in the createThing() function.

If need be, you can even convert it into a map index and even use it to get the X and Z of that particular map cell.

Wait! What? Can we actually get the X and Z too? Oh yes, we can!

In the same way we can convert X and Z into something digestible by the game’s engine and all these different functions, we can also do the opposite!

MapPosXZ

There is one structure that holds both the X and Z values and the map index of any given map cell.

It is called MapPosXZ: http://www.populous3.info/script3_doc/s … s_x_z.html

What this means is that what you can do with the index of a map cell and what you can do with the X and Z values of a map cell, you can do with MapPosXZ.

This is pretty useful for when we want to discover X and Z of our Map Element for example.

If we use our previous code, where we have just converted out Map Element into a struct Coord3D and we try to discover its X and Z coordinates, we make use of this structure. The way we do it looks like this:

local mp = MapPosXZ.new()
mp.Pos = world_coord3d_to_map_idx(c3d)
log(string.format(“X: %d and Z: %d”, mp.XZ.X, mp.XZ.Z))

What in the world is happening here?

MapPosXZ holds these two types of values in two different structures. The Pos holds the index of a map cell, while the XZ holds the X and Z of that same map cell.

In case you are wondering where this Pos and XZ.X and XZ.Z come from, we have to once again take a look into our always helpful PopScript3 Documentation, more specifically here: http://www.populous3.info/script3_doc/s … _x_z.html

We are assigning the index of the c3d cell into our mp.Pos parameter and taking the X and Z values from there. Because MapPosXZ can hold both values, this means that when you give it the map index of any map cell, it will also store its X and Z values, which are the two structures the game uses to figure out a map cell in your map, while inside the game. Finally, at the end, we are just logging out the values of X and Z into out debug output.

Let’s use an easy and simple example:

local c3d = MAP_XZ_2_WORLD_XYZ(162, 214)
local mp = MapPosXZ.new()
mp.Pos = world_coord3d_to_map_idx(c3d)
log(string.format(“X: %d and Z: %d and idx: %d”, mp.XZ.X, mp.XZ.Z, mp.Pos))

In this example we are just logging the index and the X and Z of the map cell we give. The point here is for us to understand that the struct MapPosXZ does hold both types of data.

Now that we have seen some of the cool functions and structure inside the Map Module, let us jump to the next important topic, the struct thing*.

The Module_Objects and Struct Thing

Thing is the main structure to know which is contained inside the Module_Objects; http://www.populous3.info/script3_doc/_ … ts_8h.html. Thing is a structure that functions in a slightly stranger way, which might cause some confusion early on. Thing by itself means nothing. Thing needs a reference first, so that it can associate itself as if it were that reference. If we look at the thing structure, which can be seen here:

http://www.populous3.info/script3_doc/struct_thing.html

We might understand that it has a bunch of parameters which seem to define something. Upon associating thing with an object, we can pretty much use all those parameters in order to retrieve information about that object.

Let’s us understand this with a simple example:

local blackShaman = getShaman(TRIBE_BLACK)
if blackShaman ~= nil then
if blackShaman.State == S_PERSON_DROWNING then
-- execute some kind of awesome code here!
end
end

If we look inside the Objects Module, you might notice that one of its functions is called getShaman(). This function is pretty much assigning the shaman object corresponding to the tribe you have identified to the local variable blackShaman, which we have just created. I should note that the variable blackShaman could be called tree, or car, or even asrfsiufuewr for all that matters. The goal with giving specific names to variables is so that we, or even others, do not get confused when reading our code.

Now that we have the variable blackShaman associated with an object, we can now use the thing structure to serve our needs. First, we check if the shaman is alive, because if the shaman is dead, the function getShaman() will not return anything into the variable. This means that thing.State will prompt us with an error, since you cannot call States for non-existent things, they simply cannot be found!

After checking that the shaman is alive, it’s when the true magic happens. The piece of code blackShaman.State == S_PERSON_DROWNING checks if the shaman is drowning. If she is, then that simple line of code will return true to the if statement and whatever code you have next will be run through.

You can find all these different States and Types and Models, etc, inside the Module_Defines: http://www.populous3.info/script3_doc/_ … s_8h.html

Let’s us run through a few of the most important aspects of the thing structure:

Type

Type is the category to which belongs what we want to code for. It can be of type person, T_PERSON, building, T_BUILDING, and so on, T_CREATURE, T_VEHICLE, T_SCENERY, T_GENERAL, T_EFFECT, T_SHOT, T_SHAPE, T_INTERNAL, T_SPELL. In the example above the Type is already defined when we use the getShaman() function, since the shaman is of Type T_PERSON.

Model

Model is a specific component of a category. It can be a wild, M_PERSON_WILD or a brave, M_PERSON_BRAVE, it can be a small hut, M_BUILDING_TEPEE, or a large hut, M_BUILDING_TEPEE_3, it can be a certain type of tree, M_SCENERY_TREE_2, or a trigger, M_GENERAL_TRIGGER, it can be an effect, M_EFFECT_REVEAL_FOG_AREA, or a spell, M_SPELL_FIRESTORM. Model is everything that is incorporated inside the different thing types that exist in the game. The shaman in that case is of Model M_PERSON_MEDICINE_MAN.

Owner

Relates to the owner of the thing you are coding for, thing.Owner. If I create a local variable called shaman = getShaman(TRIBE_BLACK) and I then try to log the owner of the variable shaman, it will return back the value of 6, which corresponds to the Black tribe:

local shaman = getShaman(TRIBE_BLACK)
log(string.format(shaman.Owner))

Returns 6 to the debug output.

If you happen to try to experiment to log the shaman.Type and shaman.Model, you will see that you get a numeric value, 1 and 7 respectively. That happens because every Type and Model are both defined as a string as well as a numerical value. That is why the debug outputs a value of 6 on the Owner of the shaman and not TRIBE_BLACK. You can see these values, right in front of the string which defines each of these objects, inside the Defines Module.

State

Refers to the state thing is in. It is related to any of the Types previously discussed, like S_PERSON_BEING_PREACHED, S_BUILDING_ON_FIRE, S_SCENERY_ON_FIRE, S_INTERNAL_FIGHT, and so on.

u

Contains a lot of different, varied and useful structures which you can check for. As an example, you could check for the shaman’s health:

local redShaman = getShaman(TRIBE_RED)
log(string.format(“Health: %d”, redShaman.u.Pers.Life))

Congratulations

We have arrived to end of the first part of our journey. I am hoping this wall of text was helpful in getting you to understand the main concepts that you might be needing in your AI scripting.

Starting to get used to look for info about the game inside the PopScript3 Documentation is a very important step, for you to be able to start figuring out things by yourself. Trust me, it is not an easy thing, especially if like me, you are not used to this kind of programming funkiness and everything programming is new to you.

Whenever you feel like you have just broken a barrier and that you have figured it all out, you will just hit another one. However, the joy of figuring things out is really neat. Don’t be afraid of asking those who know far more than you, about anything you might wish to know. Although sometimes it can be off putting seeing what others are able to do, while you are struggling figuring it out, channel that instead into understanding the possibilities of what, eventually, you will be able to do.

source