CustomUtilitiesPack.lua is an include file for map scripts, providing scripters with a set of tools to help make developing advanced maps simpler and easier. At present, the file is in its beta stage. This means that changes to it may not support backward compatibility. As such, I do not recommend anybody using it in a map script intended for publication until the release of version 1.00. Upon version 1.00, backward compatibility will be an important feature. This is why I wanted to first run the beta by the forum community to collect feedback. Any major changes to be made can be addressed now before it becomes an issue. The main tool that this file makes available is the AreaMap object. This allows lua tables to be manipulated as if they were a two-dimensional array. It also provides tools to manipulate this data, allowing for cropping, stretching, copying, masking, and other filters. Documentation is provided in the include file itself, explaining the various functions; however, I will provide some examples here. Code: -- To designate a variable as an area map object, call the function: local yourMap = AreaMap.Create(iWidth, iHeight, iDefault_Value, bWraps_X-axis, bWraps_y-axis) -- Now that the area map has been created, it can be manipulated in -- a number of ways. -- This will generate fractal noise on the map: yourMap:FiraxisFractal(iGrain_of_Fractal, fBlend_amount) -- The fractal grain determines roughness of the noise, and accepts -- positive integers -- The blend amount is a float ranged 0-1 that will determine how much -- the fractal will be blended with any previous data in the area map. -- For example, if we ran a second Fractal() function... yourMap:FiraxisFractal(1, 0.5) -- ... after the first one, the results of the second fractal would be -- mixed equally with that generated by the first. -- We might want numbers of a range other than 0-1, though, so next we -- will scale the map's values: yourMap:Scale(floor, ceiling, mid_point) -- The floor indicates the lowest number features on the area map while -- the ceiling will set the highest value. The mid_point attempts to -- scale the average value of all the tiles to a particular value; -- however, you can just supply "nil" for a straight scale. -- Civ5 doesn't have a lot of use for float variables (i.e. non-integers) -- so let's now see what we can do about that. To convert a map's value -- to integer form, you can use the following function: yourMap:Integer() -- or if we just wanted to abstract the map into binary values (0 or 1) -- we could do the following: yourMap:Boolean(threshold) -- THis function will set the map's tiles to either 1 or 0 depending on -- whether an individual tile is equal or greater than the threshold -- value (assigned to 1) or below the threshold value (assigned to 0). -- Note: This will probably be renamed Binary() in the next update. -- Okay, so we have this binary map... let's turn that into something -- useful! Watch closely: local iW, iH = Map.GetGridSize() local mapPlots = AreaMap.Create(iW, iH, PlotTypes.PLOT_LAND, true, false) mapPlots:Stamp(yourMap, PlotTypes.PLOT_OCEAN, 0, 0, false) -- That might look confusing at first, but let me explain what happened. -- First, we declared the variables iW and iH and assigned them the -- width and height of our game map. Then we created a new Area Map -- called mapPlots. We made mapPlots the same size as our game map, -- and filled it with the default value Civ5 uses as ocean. -- The interesting parts comes with the Stamp() function. What that did -- was took the area map we had just made binary above and used it to -- apply ocean to mapPlots. The way it works is that it looks at each -- tile in yourMap. If a tile was 0, then nothing happened. Otherwise, -- if the tile was -not- 0, then the value PlotTypes.PLOT_OCEAN was -- assigned to the comparable tile on mapPlots. -- Here's a more detailed look at what the arguments do: Stamp(map_to_apply, value_to_multiply, x-axis_offset, y-axis_offset, bTileApplicationMap) -- The first passed variable is the AreaMap to use as the stamp. It -- should first be run through a binary filter to function properly. -- The second argument is the value to apply to the map when a non-zero -- tile is found. Actually, it's a multiplier, but since all non-zero -- tiles should be 1, if you ran it through a binary filter, then the -- argument is passed directly without modification. -- The x- and y-axis offsets determine the positioning of the lower-left -- tile (index 0) will be relative the two maps. Positive values will -- mean that the stamp will occur upwards and rightward while negative -- values mean that the stamp will be positioned downwards and to the left. -- Depending on plotMap's wrapping flags, the stamp may or may not -- continue to be applied on the far boundaries of the map. -- If the function is assigned to tile the stamp, then, if the map being -- stamped is smaller than the map to which it is being stamped, the -- stamping effect will repeat horizontally and vertically. So now let's put these all together and write a function that will generate actual plots for a map script: Code: function GeneratePlotTypes(args) -- Here we're acquiring data from the game about our map local iW, iH = Map.GetGridSize(); local flags = Map.GetFractalFlags(); -- Using the map data, we're going to create an Area Map to house our plot information. local plotMap = AreaMap.Create(iW, iH, PlotTypes.PLOT_LAND, flags.WrapX, flags.WrapY); -- Now we're creating a new Area Map that is half in dimension of the game map. We have arbitrarily told it to wrap horizontally and vertically. local heightMap = AreaMap.Create(iW * .5, iH * .5, 0, true, true) heightMap:FiraxisFractal(1,1); -- Creating fractal noise heightMap:Boolean(.5); -- Running the binary filter. Since we know that the fractal generates results between 0 and 1, we can assume that a threshold value of 0.5 will cover roughly half the map. plotMap:Stamp(heightMap, PlotTypes.PLOT_HILLS, 0, 0, true); -- We've now assigned hills to our plotMap. Because we told the Stamp() to tile and because the heightMap is currently smaller than the map to which it is being applied, the hills will be stamped four times on the plot map. Because we previously told the heightMap to toroidally wrap on both the x- and y-axis, the transition of the tiling will be perfectly smooth. -- Let's now rebuild the heightMap at normal dimensions heightMap = AreaMap.Create(iW, iH, 0, flags.WrapX, flags.WrapY) heightMap:FiraxisFractal(2,1); -- We're running a new fractal, this one will be "rougher" than the first. heightMap:Boolean(.5); plotMap:Stamp(heightMap, PlotTypes.PLOT_OCEAN, 0, 0, false); -- We've now stamped the plotMap with ocean tiles -- For the sake of simplicity, we'll ignore mountains for this example and move on right to getting our data to the game map itself. I plan to write a function that will handle this but in the meantime we'll do it manually. -- The following two lines will run a nested loop that will cover each x- and y- coordinate on the map for x = 0, iW - 1, 1 do for y = 0, iH - 1, 1 do local plot = Map.GetPlot(x,y); -- We're acquiring plot data from the game map local tile = plotMap:GetHeight(x,y); -- Now we're acquiring tile data from the plotMap plot:SetPlotType(tile, false, false); -- We've just assigned the data from our plotMap to the game map. Yay! end end end This is just one example of how the AreaMap object can help make map generation easier. Other function, detailed in the include file, include but are not limited to: Copy: Similar to stamp except that it copies all tiles from the application map to the initiating map, rather than just non-zero tiles. The offset features offer fine control over placement, making this useful for developing "multi-layered" maps. Multiply: Similar to Copy except instead of overwriting values, this function will multiply the values of each map's corresponding tiles together. An excellent means of masking when used with binary filtered maps. Stretch: Will magnify, shrink or warp an area map, stretching it to supplied dimensions Canvas: Similar to stretch but only changes the dimensions of an area map without altering the value of the tiles. The map may be cropped or new areas will be filled with the map's low value. Orphan: Searches for non-zero tiles and tallies the number of adjacent non-zero tiles. If that number falls within a designated range, the tile is set to zero. This is useful for removing single-tile islands. SetHeight: The inverse of GetHeight. This will assign a value to a particular tile. Useful for people who can't quite wrap their head around the concept of using single-dimensional arrays to plot two dimensional matrices. Hex: This function returns the indices of tiles on an area map which are a specified distance from a selected tile. This is very handy for building influence maps, especially when you want to find tiles more than two tiles away. It automatically checks for map boundaries and wrapping. Solarise: Runs an area map though a solar filter. These are excellent for generating channels of water or mountain ranges, depending on its application. Fractal: This is an experimental fractal generator. While it's "functional", it doesn't yet produce acceptable results. Once I've worked out the kinks, it should generate fractals without the limitations of the one Firaxis has provided. If you've actually read this far, then you'll probably want to know how to install the include file. After downloading it from the forum, extract the archived file to the following directory: Code: [Civ5 Directory]\assets\Gameplay\Lua To use in your map script, add the following line to the top of your script file: Code: include("CustomUtilitiesPack"); As I said, though, at this stage I am primarily looking for feedback, particularly with regard to improving user-friendliness. To anyone who is a map scripter, or anybody thinking of developing one, is a more developed version of this something you think you would use? What functions not already supplied might you find useful? Do you think any of the function names could be made clearer? Also, is there a way of effectively overloading functions? I believe lua has some kind of overloading capability but, as we're working with a typeless script, I haven't figured out how to set it up. Perhaps someone with more lua experience than I can help. A word of warning, while I did perform a great deal of testing, there may still be a few bugs in the script. I'd actually be surprised if there weren't. If you run in to an exception or anomaly, please let me know and I'll try to fix it or provide a workaround as soon as possible. Thank you in advance for your patience and understanding.