Create a map script: guide and issues

Discussion in 'Civ5 - Modding Tutorials & Reference' started by LDiCesare, Oct 7, 2010.

  1. LDiCesare

    LDiCesare Deity

    Joined:
    Dec 22, 2005
    Messages:
    2,612
    Location:
    France
    Creating a map script in Civ IV was pretty straightforward: Find out where the files are located, add yours there, zip it when it works, upload..
    In Civ V the process is more complex if you want to upload the map script to the mod browser.

    Here are steps:
    1 - Fill the skeleton:
    1.1)Create a solution in ModBuddy.
    1.2)Add a new lua file to the solution, which will be your script. I suggest putting it where modbuddy proposes by default.
    This file can be located anywhere. However, if you want to test it, you will want to make changes to it and test at the same time, so a copy of this file will have to be located in your Maps folder. Putting it there however will probably conflict with the version that your mod will have installed, adn you won't know which version the game is using. Back to this issue later.
    1.3)Code. I'm not going to give details on this. The best way is to look at other existing scripts, like Fractal, Continents, etc. and see what they do, and start from that. Hopefully at some point Firaxis might release a mapscript modders guide, Sirian made a great documentation in IV, so maybe he'll do that again in V. I think the process is very similar to Civ IV, as I saw some "beautification", like 'adding lake' traces in the map generation, that I never coded in my script.
    1.4) If you want your script to support multiplayer, add
    Code:
    SupportsMultiplayer = true
    , in the GetMapsCriptInfo section. Also make sure you don't use fancy random number generation routines, as only the map seed and the script name are sent when generating multiplayer games.

    2 - Test:
    You should use the WorldBuilder for testing. It shows all the custom options of the script. However, I didn't find how to use the sizes, you can only move the sliders changing width and height. So if you want to have custom map sizes, you'll have to move the sliders everytime.
    2.1)Put your script where it can be used by the game. You can do this by simply 'Building' the project.
    Unfortunately, after more testing, this proves NOT to work. You have to also ENABLE the mod. The best way is therefore to scrap ModBuddy altogether for testing and to just put your map into the Maps folder and edit it form there. My advice is therefore: DO NOT use ModBuddy EXCEPT for releasing the mod.
    2.2)Run your script.
    A lua console window will pop up and disappear when the script finishes. This is a real pain since the error message that will inevitably happen will vanish as soon as it is shown. Sometimes the window will close before the error traceback is even printed. Maybe the output of this console is logged somewhere, but I couldn't locate where.
    2.3)Debug.
    I haven't found anything more efficient than using a wait() loop in which I print debug info, then do nothing for a long while hoping the window will therefore remain opened long enough for me to see my traces.
    However, you can't edit your file and save, you must build. The effect of Build is that it will save your file, zip your file and put it in a folder you have no use of right now, and most importantly copy the saved file to the folder that Civ and the worldbuilder use.
    This is quite easy if you like the ModBuddy editor, but if you use an external editor, then you're better off writing directly the file in the Maps or Mods folder, but you'll have to copy that file back into your project later.
    Overall, if you like visual studio editor, you can happily use it, just remember to use Build and not save, even though Build's only use is a save as. If you like another editor, the IDE will just make your life harder for not using it.

    3 - Test again
    Test again, but in-game, just to be sure.
    Note you can only test players placement in game.

    4 - Distribute
    When you're happy with your script, you should look in the Packages directory of ModBuddy, where your civ5mod is located. You can then upload it by going to Online tools in ModBuddy, logging (everytime, won't save your login from last session) and uploading your file.
    I strongly suggest uploading it as a private mod first, then removign whatever versions you might have in your local directories, and downloading it to test. It's probably just paranoia, but still. Then make it public (need to click the View Details in the online tools window) after you're sure it actually works after being downloaded. The IDE is supposed to make sure this is unnecessary, but if you put a file somewhere unexpected, it might work on your system for that reason, so a bit of cleanup for testing might be useful.

    Comments:
    The output and error messages in WorldBuilder are a pain. The window closes after an error happens, which may be fine when you're just generating a map to start from, but frankly when I make up a map I always start from scratch or an existing map, never from a map script so I miss the point of closing the window. A checkbox to close it always, along with an option to change that setting, would do a great deal to help mapscripters in my opinion.
    Having to use ModBuddy when you have your own favorite editor is a real pain. Were it not for the md5 sum, I'd happily scratch the thing and use 7zip to create the package myself.
    Also, at the end of the process, you should have several copies of the lua script scattered around your hard drive: One in the project directory, one in the mods directory, one hidden in the package. I think it's a waste of hard drive space, but then these are really small files, so not a big concern. I'd be much more concerned for bigger mods, particularly those with art assets, where such duplication seems very bad.
    I don't know if the project can use its output as an input, I doubt it, but it would avoid the multiplication of files.

    Wishes:
    Two things would be particularly helpful to help mapscripting:
    -Having an option to keep the lua console open in WorldBuilder when script generation finishes.
    -Documentation of the lua functions available and the overall process (succession of method calls in particular).
     
  2. Nercury

    Nercury Warlord

    Joined:
    Aug 5, 2006
    Messages:
    126
    Location:
    Lithuania
    Very useful. Can I add it into wiki?
     
  3. deanej

    deanej Deity

    Joined:
    Apr 8, 2006
    Messages:
    4,859
    Location:
    New York State
  4. LDiCesare

    LDiCesare Deity

    Joined:
    Dec 22, 2005
    Messages:
    2,612
    Location:
    France
    Of course.
     
  5. aimlessgun

    aimlessgun King

    Joined:
    Jan 4, 2010
    Messages:
    782
    Are there any tutorials anywhere for the coding part of making mapscripts?
     
  6. Nercury

    Nercury Warlord

    Joined:
    Aug 5, 2006
    Messages:
    126
    Location:
    Lithuania
    Not yet. But your examples are in maps folder. If you have any questions, ask.
     
  7. Drawmeus

    Drawmeus Emperor

    Joined:
    Jul 25, 2007
    Messages:
    1,213
    I intend to make at least one map script, though I don't really know how to get started.

    I'm reasonably proficient in lua, totally proficient in xml and reasonably proficient in C++ (though I gather I don't need any C++ for a map script).

    Would you have any particularly easy or well-commented example map script to follow? The information I'm missing is more good-practices stuff than technical stuff; Implementing things is easy, but I don't know how to design a good map script.

    I'll be able to work it out for myself, but if you've got a good reference or example (or can pick on one of the examples included with Civ 5 as particularly easy) I'd appreciate it.
     
  8. madAzrael

    madAzrael Chieftain

    Joined:
    Dec 2, 2005
    Messages:
    9
    ditto ^^

    i looked at the scripts, but i also dont know where to start.

    it's getting very technical now... you've been warned

    this is what i expected in my ... unsophisticatedness:
    Some functions to set size of the map, then set the terrain type, features, etc on specific coordinates. do that for all coordinates and you're done

    well it doesnt work like that, does it?
    let's see. Nearly every mapscript uses FractalWorld as a basis, correct?
    And there is this MultilayeredFractal with some nice comments and i guess understanding those scripts can be very helpful (or is essential?). But for now i want to poke around and look what i can find.
    And that's how i got to methods like
    table.fill(PlotTypes.PLOT_OCEAN, gridWidth * gridHeight)
    and
    table.insert(land_totals, current_column);

    This is for filling the database? So a map is basicly a filled database-table? Does anyone know the schema of this table, or even more important: is this object "table" and its methods documented somewhere?

    The other methods i found without any comment what they do are:
    Fractal.Create(....)
    Fractal.CreateRifts(...)

    If i find such methods and dont know what they do, where can i look it up?
    grep (=fulltextsearch for all windowsusers out there) over all sourcefiles and hope the best?

    if you have answers, please share :)
     
  9. aimlessgun

    aimlessgun King

    Joined:
    Jan 4, 2010
    Messages:
    782
    Yeah the mapscripts I looked at are pretty opaque. Especially Fractal, it tells me essentially nothing! Wanted to modify the Archipelago script to replace all coast/ocean tiles with desert, but since it runs off fractal I couldn't even get a sense of where to start with that.

    EDIT: Or if I gave up and wanted to put in a request for a mapscript, where would I post that haha.
     
  10. LDiCesare

    LDiCesare Deity

    Joined:
    Dec 22, 2005
    Messages:
    2,612
    Location:
    France
    A short description:
    The main functions of a map script are:
    function GeneratePlotTypes()
    -- stuff
    end
    function GenerateTerrain()
    -- stuff
    end
    function AddFeatures()
    -- stuff
    end
    The first generates mountains/hills/lands/water.
    The second generates desert/plains/tundra...
    The last adds forests and the like if I'm not mistaken.
     
  11. LDiCesare

    LDiCesare Deity

    Joined:
    Dec 22, 2005
    Messages:
    2,612
    Location:
    France
    GeneratePlotTypes is the only function I've changed so far.
    It does 2 things:
    Code:
        SetPlotTypes(plotTypes)
        local args = { bExpandCoasts = false }
        GenerateCoasts(args)
    GenerateCoasts allows you to decide whether coast terrains should expand and span across seas or not.
    SetPlotTypes is where most of the stuff goes. It has one argument, which is a table. It is indexed from 1 to mapwidth*mapheight.
    You can retrieve map size with:
    Code:
    local iW, iH = Map.GetGridSize()
    Which you can then use to compute plots something like this:
    Code:
            for y = 0, iH - 1 do
                for x = 0, iW - 1 do
                    local plotIndex = y * iW + x + 1
                    local height = self.heightMap[plotIndex]
                    if height < self.landPlotHeight then
                        self.plotTypes[plotIndex] = PlotTypes.PLOT_OCEAN
                    elseif height < self.hillPlotHeight then 
                        self.plotTypes[plotIndex] = PlotTypes.PLOT_LAND
                    elseif height < self.mountainPlotHeight then
                        self.plotTypes[plotIndex] = PlotTypes.PLOT_HILLS
                    else
                        self.plotTypes[plotIndex] = PlotTypes.PLOT_MOUNTAIN
                    end
                end
            end
    In this example, self.plotTypes is a table which I feed to SetPlotTypes
    Of course, how you decide of a heightmap, whether you use one at all, depends on your map script, I just mean to show what the expected structure looks like.

    Here's one useful set of functions too:
    Spoiler :
    Code:
        South = function(self, x, y)
            if y == 0 then return nil end
            return (y-1)*self.iW + x + 1
        end,
        North = function(self, x, y)
            if y == self.iH - 1 then return nil end
            return (y+1)*self.iW + x + 1
        end,
        West = function(self, x, y)
            if x == 0 then return y*self.iW + self.iW - 1 +1 end
            return y*self.iW + x - 1 + 1
        end,
        East = function(self, x, y)
            if x == self.iW - 1 then return y*self.iW + 1 end
            return y*self.iW + x + 1 + 1
        end,
        NorthWest = function(self, x, y)
            if y == self.iH - 1 then return nil end
            return self:West(x, y + 1)
        end,
        SouthWest = function(self, x, y)
            if y == 0 then return nil end
            return self:West(x, y - 1)
        end,
        Neighbours = function(self, x, y)
            local result = {}
            result["South"] = self:South(x, y)
            result["North"] = self:North(x, y)
            result["West"] = self:West(x, y)
            result["East"] = self:East(x, y)
            result["NorthWest"] = self:NorthWest(x, y)
            result["SouthWest"] = self:SouthWest(x, y)
            return result
        end,

    These will give you the neighbour tiles for a x,y coordinate starting at 0,0.

    Hope this helps.
     
  12. LDiCesare

    LDiCesare Deity

    Joined:
    Dec 22, 2005
    Messages:
    2,612
    Location:
    France
    Note that, unless I'm mistaken, only the first of the three aforementioned functions is data-agnostic. Land/sea/hills/mountains are hardcoded in the game, whereas tundra, forests, etc. are probably xml-defined and therefore can depend on the mods you're using.
     
  13. aimlessgun

    aimlessgun King

    Joined:
    Jan 4, 2010
    Messages:
    782
    Thanks for the help LDi. For myself though I think I'm pretty hopeless short of a very seriously complete tutorial/guide. It seems I have no idea exactly what the process for generating maps is. I don't even know why those North, South etc functions are useful haha.
     
  14. SamBC

    SamBC Emperor

    Joined:
    Feb 2, 2010
    Messages:
    1,052
    Location:
    Lancaster
    'table' is a set of standard library functions in lua, for manipulating tables. Tables are one of the basic data types in lua, taking the place of arrays, lists, and so on from other languages. They are essentially the basis of all compound datatypes. The first argument to their functions is usually the table to be manipulated.
     
  15. Nercury

    Nercury Warlord

    Joined:
    Aug 5, 2006
    Messages:
    126
    Location:
    Lithuania
    Unless I am mistaken, most default map scripts use Fractal in one way or another. However, there is no need for that!

    "plotTypes" in "SetPlotTypes(plotTypes)" is an array of indices and plot types which starts from lower left corner and ends at upper right corner. To convert x, y to index in this array use this function ("width" must be accessible current map width):

    Code:
    getOffset = function(x, y)
    	return width * y + x + 1
    end
    
    "Offset" is that index on this single array that represents tile at x, y. To convert offset back to x, y, do simple backwards math:

    Code:
    getXY = function(offset)
    	offset = offset - 1
    	return offset % width, math.floor(offset / width) -- lua can return two variables
    end
    
    I have written this simple class to hold info about map:

    Spoiler :
    Code:
    MapInfo = function(width, height, isWrapX, isWrapY)
    	self = {
    		width = width,
    		height = height,
    		isWrapX = isWrapX,
    		isWrapY = isWrapY,
    		-- adjust out-of-bounds tile if it is wrapped in x direction
    		fixWrapX = function(x)
    			if isWrapX then
    				x = x % width
    			end
    			return x
    		end,
    		-- adjust out-of-bounds tile if it is wrapped in y direction
    		fixWrapY = function(y)
    			if isWrapY then
    				y = y % height
    			end
    			return y
    		end,
    		-- adjust both x and y of out-of-bounds tile
    		fixWrap = function(x, y)
    			return self.fixWrapX(x), self.fixWrapY(y)
    		end,
    		-- get x, y 1-based offset
    		getOffset = function(x, y)
    			x, y = self.fixWrap(x, y)
    			return width * y + x + 1
    		end,
    		-- get x, y as 0-based indices from 1-based offset
    		getXY = function(offset)
    			offset = offset - 1
    			return offset % width, math.floor(offset / width)
    		end,
    	}
    
    	return self
    end
    


    So, once you create your array of plot types, you pass it into SetPlotTypes, where is gets converted into appropriate terrain type for every plot type (that function is in lua, do a "search in files" to find location of it, it is small, and I made my own version to fix some issues).

    GenerateCoasts creates coast tiles where appropriate. You can check my "Olympic Rings" map script. You will see that

    - GetMapScriptInfo() is declared because I want to change what default options are.
    - GeneratePlotTypes() is an example of usage of some different algorithm (not fractal). I translate perfectly round rings into hex tiles at appropriate locations and change plot types at those locations (with some randomness added). "pg.setPlotTypes" is my own version of SetPlotTypes, is is basically the same but I wrap plot type inside an object (because I will need to put more info there later), therefore the change.
    - GenerateTerrain(), AddFeatures(), StartPlotSystem() are 1:1 copied from some other map which uses some climate options to have some control over generated climate.
    - GenerateMap() is main function that runs all "susbystems". There is no need to re-declare it here, but it is useful when creating map script to temporarily "shut down" unneeded things.

    You are welcome to copy/paste functions from me, modify them to your needs, just give me a small credit somewhere. And use "BeeHelpers" from "Circle of War", because it is newer there and has many bugs fixed.

    You can test your map script over WorldBuilder "new map". Much quicker there. Just load your mod before you do that.
     
  16. Nercury

    Nercury Warlord

    Joined:
    Aug 5, 2006
    Messages:
    126
    Location:
    Lithuania
    Because every odd line is shifted to the right to form hex tiles. Therefore it is no longer up/down/right/left, but 6 directions.

    Incidentally, if map height is not even, functions that fix Y coordinate (when map is wrapped in Y direction) do not work correctly. In such a case X coordinate depends on Y. But everything seems to be fine if height is even. But there seems to be some more issues (Firaxis has disabled Y-wrap for now it seems) :)
     
  17. madAzrael

    madAzrael Chieftain

    Joined:
    Dec 2, 2005
    Messages:
    9
    Thanks guys.
    That looks indeed helpful.

    I propably should refresh and extend my very basic lua-knowledge a bit, so i dont ask question like 'what is a table' again... :mischief:
     
  18. Nercury

    Nercury Warlord

    Joined:
    Aug 5, 2006
    Messages:
    126
    Location:
    Lithuania
    Well I had no Lua knowledge when I got started: my breakthrough happened when I just realized that I can use it like javascript (just with "end"s everywhere :) ).

    EDIT: oh great I noticed an error in MapInfo I posted... "self" should be "local" there...
     
  19. LDiCesare

    LDiCesare Deity

    Joined:
    Dec 22, 2005
    Messages:
    2,612
    Location:
    France
    Edited first post because ModBuddy is actually a REAL PAIN. Building the project is not enough, unless you already activated the mod or something. Since figuring 'or something' is painful, since ModBuddy is useful only insofar it provides packaging options and a text editor, since there are tons of better text editors out there, just scrap ModBuddy.
     
  20. JeBuS27

    JeBuS27 Heretic

    Joined:
    Sep 21, 2005
    Messages:
    321
    After you fix the Lua language service (look in the Utilities forum), ModBuddy has everything you need. Telling people to use other tools is just likely to create confusion.
     

Share This Page