"Blank" Map Script?

cassembler

typically in screensaver mode
Joined
Dec 17, 2001
Messages
234
Location
Texas
I'd like to play around with map scripts, but don't have a ton of programming experience. The language looks easy enough, but I'd like to start with as minimum code as possible- a no-frills generator.

Can someone tell me what the bare minimum is to create a basic fractal map? Or, if there's a tutorial...
 
I'd suggest taking an existing map you like and start by just altering a few values and see what happens. Then get a feel for the script itself and begin to make small structural changes until you feel ready to start building your own functions. It would be helpful if you had programming experience to draw upon, but it's not necessary. Civ5 uses Lua script so I'm sure you can find tutorials by searching the internet. You'll probably learn most, though, by looking through existing scripts and copying what other developers have done.

One important thing for all Civ5 scripters to know. When something goes wrong (and it -will-), you can see exactly where the error occurred in the lua.log file. This is located:

[User Directory]\Documents\My Games\Sid Meier's Civilization 5\Logs

Always remember to try to document your work. That means, as you're developing your map, explain in comments what each portion of script is intended to do. Not only does this make it easier for other people to understand your work but it will allow you to remember what you were trying to do when you look at it again after a long lapse. You don't have to be diligent about it, but at least try and explain the major or esoteric features. While it may seem like unnecessary work, it's a good habit to have.

Hope that gets you on your way. Good luck!
 
The most basic, barebones, map script is the Fractal.lua (found somewhere like this: C:\Program Files\Steam\steamapps\common\sid meier's civilization v\Assets\Maps )

You probably want to override only the GeneratePlotTypes method, but the above script doesn't tell you how.
You could look at the Continents.lua version, which is a bit big, but gives good ideas on how to use fractals in my opinion, and I think is the best starting point if you want to go with fractal generation.

There are also a few very useful files to look at in
C:\Program Files\Steam\steamapps\common\sid meier's civilization v\Assets\Gameplay\Lua
MapGenerator.lua explains the process of which function is called in which order.
 
Very nice, thanks both of you. I'm planning a "idiot's guide to a basic map script" or something, written by an amatuer. We'll see...
 
LdiCesare's right. There's a lot of good stuff, including a lot of useful notes provided by Sirian, in the files of the Assets/Gameplay/Lua folder. Just be careful you don't actually edit any of these files because changes made there will affect every single script that refers to them.

Fortunately, copying any function from the core files to your script will cause Civ5 to read the one in your script instead of the normal version. Let me try to give you an example.

In TerrainGenerator.Lua there's a function called "TerrainGenerator:GetLatitudeAtPlot(iX, iY)". What this function does is take a plot and determine by its position on the y-axis its latitude, returning a value from 0 (equator/center) to 1 (pole, north or south bound). The is useful to determining where to place jungles versus forests, snow versus desert, etc.

Let's pretend that you want to make a regional map where the warmest band isn't in the center but at either the north or south bound of the map. What you would do is make a "TerrainGenerator:GetLatitudeAtPlot(iX, iY)" in your script and supply new code that would return the revised values. Now every time that function is called for that map, it will process your new script instead of the base script in TerrainGenerator.Lua. At the same time, since the original file has not been altered, no map scripts other than your own will be affected by the change.

So, in short, don't change any of the files in the Assets\Gameplay\Lua\ directory. If you want to change how these work, copy the relevant passages to your script and edit them there.

Good luck with the tutorial!
 
give me 20 minutes, and the chance to get my post count to 30(otherwise, I have to wait to have it approved, and I'd rather not bother them), and I will upload something you may find useful.

I'll go spam my own thread now, to get those last few (unethical I know, but its for a good cause!)

Be right back!
 
Fixed bug, overwrote original text of my post when editing.... hope this helps you. cut and paste. comments encouraged and appreciated!

enough for tonight, im off to bed!

EDIT2: yet another oversight.... that's what i get for trying to rush sorry for the confusion!!!
EDIT3: there's a lesson in this I'm sure... something about testing before release.... anyway, caught one more oversight with continents_grain. it works. I'll correct any outstanding issues tomorrow. Really off to bed this time.

Code:
------------------------------------------------------------------------------
--	FILE:	 FractalMapOptionsPlayground.lua
--	AUTHOR:  Jamie McQueen (Skweetis)
--	PURPOSE: Map script for exploring Fractal options and their effects during
--			 Map Generation. By a mapscript virgin for mapscript virgins.  
--			 Some options will hang worldbuilder
--			 in certain cases (I.E low continent_grain value and extra_mountains
--			 value above 6 for example.  Generator log printstatements allow
--			 debugging of values.  Best when used with UNOFFICIAL patched 
--			 WorldBuilder version 4 or better (available at www.civfanatics.com)
--			 which keeps the log window open during map generation among other 
--			 improvements.
--			 I have included what comments I can, and have laid it out in a way
--			 that I find easiest to follow. By no means is this reflective of how
--			 things should be laid out.  I have only hobby
--			 level programming experience and am not a coding expert by any stretch.
--			 It has helped me immensly though, in figuring out what things do,
--			 And learning to create mapscripts. As this was created as a personal
--			 reference tool, it has not been tested in-game, though I see no 
--			 reason why it should not work.  Hopefully other budding mapscripters
--			 will find it useful.   I suggest looking through this while also
--			 refering to FractalWorlds.lua in ../GamePlay/Lua/. which is really
--			 the blood and guts of fractal world building, and includes numerous
--			 helpfull comments left by Bob and Brian (some referenced in here)
--			 Enjoy and good luck exploring! -jm
--			 Special thanks to ColBashar for his input on my map script, his
--			 advice helped align me in the direction I needed to go. Turned
--			 into a public tool because of cassembler's request for a blank
--			 map. 
------------------------------------------------------------------------------
--	Copyright (c) 2010 Firaxis Games, Inc. All rights reserved.
------------------------------------------------------------------------------

--what other lua files have code in them we will call in our map script are listed here.
include("MapGenerator");
include("FractalWorld");
include("FeatureGenerator");
include("TerrainGenerator");

------------------------------------------------------------------------------
function GetMapScriptInfo() --somebody else would be better to describe it, I'm sure my explaination would cause more confusion than knowledge.
	local world_age, temperature, rainfall, sea_level, resources = GetCoreMapOptions() -- 'local' statements are where how declare a variable.
	return { -- somebody else can explain returns better.
		Name = "FractalMapOptionsPlayground",
		Description = "A very customisable fractal based world generator.",
		IsAdvancedMap = false, -- setting to True will make this map only appear when Advanced button is clicked
		IconIndex = 16,
		SortIndex = 1,
		CustomOptions = {
		world_age, temperature, rainfall, sea_level, resources, --CustomOption index +5 (5 core map options, which are defaulted to -99 to -95 so as to always come first)
			{
			Name = "Continent Grain", -- Map.GetCustomOption(6) <<I do this only for reference to help remember what Option Pulldown information I am getting when putting in a variable (see next function)
				Values = { -- play and see!  basically smaller values make larger continents or a pangaea, larger will result in scattered islands.
					"1", -- DefaultValue means this will be the first option displayed.  Index (count) starts at 1 and counts up for each entry.  Text is only used in pull down.  Script takes index only.
					"2", -- index value 2
					"3",
					"4",
					"5",
					"6",
					"TXT_KEY_MAP_OPTION_RANDOM"
				},
				DefaultValue = 1, -- which index position will be displayed by default in pulldown. 
				SortPriority = 1, -- which order this option pulldown will be displayed. 
			},
			{
			Name = "Center Rift?", -- Map.GetCustomOption(7)
				Values = { -- puts an ocean rift on the map to difinitively split continents apart. Essentially creates at least two separate land masses.
					"No", -- Default
					"Yes",					
					"TXT_KEY_MAP_OPTION_RANDOM"
				},
				DefaultValue = 1,
				SortPriority = 2,
			},
			{
			Name = "Rift Grain", -- Map.GetCustomOption(8)
				Values = {-- -1 is Default no rifts. Set grain to between 1 and 3 to add rifts. - Bob(FractalWorld.lua)
					"-1", -- Default
					"1",
					"2",
					"TXT_KEY_MAP_OPTION_RANDOM"-- Default
				},
				DefaultValue = 1,
				SortPriority = 3,
			},
			{
			Name = "Has Tectonic Islands?", -- Map.GetCustomOption(9)
				Values = {-- Build islands in oceans along tectonic ridge lines - Brian(FractalWorlds.lua)
					"No", -- Default
					"Yes",
					"TXT_KEY_MAP_OPTION_RANDOM" 
				},
				DefaultValue = 1,
				SortPriority = 4,
			},
			{
			Name = "Polar?", -- Map.GetCustomOption(10)
				Values = { -- I'm not clear exactly on what this does yet -jm
					"No", -- Default
					"Yes",
					"TXT_KEY_MAP_OPTION_RANDOM" 
				},
				DefaultValue = 1,
				SortPriority = 5,
			},
			{
			Name = "Extra Mountains", -- Map.GetCustomOption(11)
				Values = {
					"0", -- Default
					"1",
					"2",
					"3",
					"4",
					"5",
					"6",
					"7",
					"8",
					"9",
					"10",
					"TXT_KEY_MAP_OPTION_RANDOM" 
				},
				DefaultValue = 1,
				SortPriority = 6,
			},
			{
			Name = "Adjust Plates", -- Map.GetCustomOption(12)
				Values = { 
					"0", -- Default
					"1",
					"1.5",
					"3",
					"TXT_KEY_MAP_OPTION_RANDOM"
				},
				DefaultValue = 1,
				SortPriority = 7,
			},
			{
			Name = "Player Distribution",-- Map.GetCustomOption(13)
				Values = {
					"Old World",
					"Continental",
					"Map Rectangle",
					"TXT_KEY_MAP_OPTION_RANDOM"-- DefaultValue = 4,
				},
				DefaultValue = 4, 
				SortPriority = 8
			},
		};		
	};
end
------------------------------------------------------------------------------

------------------------------------------------------------------------------
function GeneratePlotTypes()
	print("Generating Plot Types (Lua Worlds Unlimited) ..."); -- these statements only printout in the log.  Useful for debugging, gives you a reference point when (and it will) your script blows up in the builder.  Last print you see, gives you an idea where to look in your script.

	-- collect user options and put them in a variable.   I do it this way, there are many other methods, likely better coding practice, but
	-- I found this way easier to follow while learning. The index of the option selected above is what we pass to the variable so we can do
	-- stuff with it.
	local sea_level = Map.GetCustomOption(4) -- my translation: "make me a container called sea_level and put the value the Map Option
	local world_age = Map.GetCustomOption(1)	
	local continent_grain = Map.GetCustomOption(6) --this is the first custom option in the above function.
	local rift_bool = Map.GetCustomOption(7) -- bool is short for boolean (true or false) Im in the habit of getting the numerical value, and turn it in to actual true or false after. 
	local rift_grain = Map.GetCustomOption(8)
	local islands_bool = Map.GetCustomOption(9)
	local polar_bool = Map.GetCustomOption(10)
	local extra_mountains = Map.GetCustomOption(11)
	local adjust_plates = Map.GetCustomOption(12)
	
	--default to false, I find it good practice to always 'initialise' your variables (ie, make sure they = something).  I can't remember why though I'm sure there is a good reason! The pros always seem to do it!
	local has_center_rift = false;
	local tectonic_islands = false;
	local polar = false;

	-- calculate random selections, and modify any values
	if sea_level == 4 then -- user selected random, so we are going to select from one of the other options in the list.  Remember, we use the pulldown option index value, the text in quotes means nothing to the script.
		sea_level = 1 + Map.Rand(3, "Random Sea Level - Lua"); -- this one is a bit longer to explain.  try and take a good look at it and see if you can figure out and understand what is happening here (a good habit and skill to practice, imo) if not, make sure you take a peak at one of the tutorial's for Civ5 Map scripting out there.
	end	
	print("DEBUG - Sea Level: ", sea_level); -- we want to see what was picked, this desplays it in the log.  notice how it is written, recognise anything familiar?

	if world_age == 4 then -- always always always try to remember to make sure you use two == when doing an 'if' evaluation. pain in the ass to find when you have a big script and cant find out why it isn't working!
		world_age = 1 + Map.Rand(3, "Random World Age - Lua");
	end
	print("DEBUG - World Age: ", world_age);

	if continent_grain == 7 then
		continent_grain = 1 + Map.Rand(6, "Random Continent Grain - Lua"); 
	end
	print("DEBUG - Continent Grain: ", continent_grain);

	if rift_bool == 3 then
		rift_bool = 1 + Map.Rand(2, "Random Has Rift - Lua"); -- don't add 1, this is boolean.
	end
	if rift_bool == 2 then
		has_center_rift = true;  -- remember we initialised this to false above? well if it isn't random, and they didn't pick the first option (which would be false, and this variable is already set to false) the only option they have left is 2, which translates to true.
	end
	print("DEBUG - Has Center Rift: ", has_center_rift);

	if rift_grain == 4 then
		rift_grain = Map.Rand(3, "Random Continent Grain - Lua"); -- no need to add 1, because randomising resets the index start count to 0, and the value we need from this option is 0.
	else
		rift_grain = rift_grain - 1-- we still need that value to be 0, so if random was not selected, the index count would be 1 if the user selected the first value, and we need 0 for this option, so lets take one away from that value.
	end
	if rift_grain < 1 then -- only do this if the value is 0 or less
		rift_grain = -1; -- we needed the 0, but it is not a valid option, and the first for this list is -1, so this here turns that 0 we can't use into a zero we can.
	end	
	print("DEBUG - Rift Grain: ", rift_grain);

	if polar == 3 then
		polar = 1 + Map.Rand(2, "Random Polar - Lua");
	end
	if polar_bool == 2 then
		polar = true;
	end
	print("DEBUG - Polar: ", polar);

	if islands_bool == 3 then
		islands_bool = 1 + Map.Rand(2, "Random Tectonic Islands - Lua"); 
	end
	if islands_bool == 2 then
		tectonic_islands = true;
	end
	print("DEBUG - Tectonic Islands: ", tectonic_islands);

	if extra_mountains == 12 then
		extra_mountains = Map.Rand(11, "Random Extra Mountains - Lua"); -- no need to add 1, starts at 0
	else
		extra_mountains = extra_mountains -1;
	end
	print("DEBUG - Extra Mountains: ", extra_mountains);

	if adjust_plates == 5 then
		adjust_plates = 1 + Map.Rand(4, "Random Adjust Plates - Lua");
	end
	if adjust_plates == 1 then
		adjust_plates = 0;
	elseif adjust_plates == 2 then
		adjust_plates = 1;
	elseif adjust_plates == 3 then
		adjust_plates = 1.5;	
	elseif adjust_plates == 4 then
		adjust_plates = 3;
	end
	print("DEBUG - Adjust Plates: ", adjust_plates);


	local fractal_world = FractalWorld.Create(); -- the way I undertand it, this gets the code from the Create() function in the file FractalWorld, the . is the separator, and it gets stored in our variable called fractal_world which we will use below.  It may just be that its easier to type fractal_world than FractalWorld.Create() repeatedly... either way we are calling a function from another file we included at the top of our script.
	fractal_world:InitFractal{ -- passing all the values between the braces, each variable separated by commas. the duplicate names may be redundant, but it makes it quicker to play change the value of any of those variables.
		continent_grain = continent_grain,
		rift_grain = rift_grain, 
		has_center_rift = has_center_rift, 
		polar = polar,
		};

	local args = {
		sea_level = sea_level,
		world_age = world_age,
		sea_level_low = 69,
		sea_level_normal = 75,
		sea_level_high = 80,
		extra_mountains = extra_mountains,
		adjust_plates = adjust_plates,
		has_center_rift = has_center_rift,
		tectonic_islands = tectonic_islands,
		};
	local plotTypes = fractal_world:GeneratePlotTypes(args);
	
	SetPlotTypes(plotTypes);
	GenerateCoasts();
end
------------------------------------------------------------------------------
function GenerateTerrain()
	print("Generating Terrain (Lua Worlds Unlimited) ...");
	
	-- Get Temperature setting input by user.
	local temp = Map.GetCustomOption(2)
	if temp == 4 then
		temp = 1 + Map.Rand(3, "Random Temperature");
	end

	local args = {temperature = temp};
	local terraingen = TerrainGenerator.Create(args);

	terrainTypes = terraingen:GenerateTerrain();
	
	SetTerrainTypes(terrainTypes);
end
------------------------------------------------------------------------------
function AddFeatures()
	print("Adding Features (Lua Worlds Unlimited) ...");

	-- Get Rainfall setting input by user.
	local rain = Map.GetCustomOption(3)
	if rain == 4 then
		rain = 1 + Map.Rand(3, "Random Rainfall - Lua");
	end
	
	local args = {rainfall = rain}
	local featuregen = FeatureGenerator.Create(args);

	-- False parameter removes mountains from coastlines.
	featuregen:AddFeatures();
end
------------------------------------------------------------------------------
function StartPlotSystem()
	-- Get Resources setting input by user.
	local res = Map.GetCustomOption(5)
	local start_method = Map.GetCustomOption(13)

	if res == 6 then
		res = 1 + Map.Rand(3, "Random Resources Option");
	end
		
	if start_method == 4 then
		start_method = 1 + Map.Rand(3, "Random Player Distribution");
	end

	print("Creating start plot database.");
	local start_plot_database = AssignStartingPlots.Create()
	
	print("Dividing the map in to Regions.");
	local args = {
		method = start_method,
		resources = res,
		};
	start_plot_database:GenerateRegions(args)

	print("Choosing start locations for civilizations.");
	start_plot_database:ChooseLocations()
	
	print("Normalizing start locations and assigning them to Players.");
	start_plot_database:BalanceAndAssign()

	print("Placing Natural Wonders.");
	start_plot_database:PlaceNaturalWonders()

	print("Placing Resources and City States.");
	start_plot_database:PlaceResourcesAndCityStates()
end
------------------------------------------------------------------------------

EDIT1:Small bug fixed....sorry
 
That's a nice tutorial script, Skweetis. I like how you turned most of the map features in to user-options, making it easy to see for one's self what each one does, and especially how you documented it. <s>

The point on initialising variables. The reason this is done is that in languages such as C++, if you declared a variable without initialising it then there was a chance it would contain artifact data from whatever was previously in memory. There was no "blank" or default value. In Lua, since variables have no type specification, the default value is "nil", which is not the same as zero. If you tried to use a nil (or null) value in a mathematic operation then you'll get an error. I know from experience. <W> I haven't tried it but I don't think nil can be used in boolean operations either.

Another important reason to initialise variables is you make your code more readable. This is just as important as the technical reasons mentioned above. Readable code is professional code. By initialising your variables prior to their actual use, you can consolidate default values in one section of script where they can be easily accessed and altered.

This is especially useful in terms of replacing "magic numbers". A magic number is a value that is harcoded in digit form. e.g. 3, or 72, or 5.917, etc. By themselves, these numbers don't mean very much, do they? So when you see them used in code, it's not always readily apparent what they're supposed to do. By replacing magic numbers with previously initialised variables, and naming those variables in a descriptive manner, you make your code more readable.

For example:

Code:
-- Instead of:
local screen_ratio = 16 / 10;

-- You could write:
local screen_width = 16;
local screen_height = 10;

local screen_ratio = screen_width / screen_height

Okay, I admit that that's a very simplistic example. When you have large blocks of code written up, though, using descriptive labels instead of generic numbers will go a long way toward making it easier for people to understand how your code is intended to work.

One more point to make. You'll see these practices a lot in Sirians scripts. You'll see it less often in mine. He's taking the professional path and I'm taking the path of least resistance. Most of the time it's perfectly okay to take shortcuts, particularly when you're writing solely for yourself or when you're experimenting and don't yet have a clear vision of where you're going. Knowing when to be diligent is the trick. Since most people reading this post will be neophytes to programming, it's important that you know good practice. You don't necessarily have to follow it, but being aware will prevent you from unconsciously forming bad habits.
 
I haven't tried it but I don't think nil can be used in boolean operations either.
On the contrary. nil is worth false in boolean operations.
A very common way of initialising stuff (or not) is to write stuff like this:
Code:
variable = variable or InitializeVariable()
The InitializeVariable will only be called if variable is nil, which hopefully means you didn't initialise it (it may have been set to nil on purpose).

Also note that the ";" at end of lines are usually useless and that people who are used to writing lua usually don't write them since thye clutter the code and serve no purpose as far as the interpreted is concerned. They just make the code look like it was ported from C.
 
Thank you, LDiCesare. I didn't know that, though I was scratching my head at why Civ5 didn't throw up an error when I noticed I forgot a semi-colon. Since this means Lua doesn't treat carriage returns as whitespace, could you tell me how to span script over multiple lines? It's not something I do often but it is sometimes convenient.
 
Carriage returns are the equivalent of semicolons id I'm right. You only need a semicolon when you want to write two statements normally put on several lines on one line.
For instance:
Code:
local f; f = function () body end
could also be written
Code:
local f
f = function () body end
You can check the syntax here if you can read a BNF, but it's not clear about line feeds.

I don't think you can write
Code:
local a = 1 + 2
+3 +4
but I never tried.
 
Neither returns nor semicolons are needed to separate statements. Breaking up a statement onto multiple lines should not be a problem. Quoting Programming in Lua:
Each piece of code that Lua executes, such as a file or a single line in interactive mode, is a chunk. More specifically, a chunk is simply a sequence of statements.

A semicolon may optionally follow any statement. Usually, I use semicolons only to separate two or more statements written in the same line, but this is just a convention. Line breaks play no role in Lua's syntax; for instance, the following four chunks are all valid and equivalent:

a = 1
b = a*2

a = 1;
b = a*2;

a = 1 ; b = a*2

a = 1 b = a*2 -- ugly, but valid
 
Back
Top Bottom