1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

PerfectWorld3

Discussion in 'Civ5 - Map Scripts' started by cephalo, Nov 25, 2010.

  1. SlightlyMad

    SlightlyMad Prince

    Joined:
    Sep 26, 2007
    Messages:
    476
    Location:
    Baltimore, MD
    Yeah, there's rendering bugs aside from just the wrong plots showing up though on non-flat land. For instance, a river that runs across the seam will frequently appear empty, a ranged attack over the scene or a unit traveling over the seam will often appear to go off screen, and any sounds associated wit it will not play. There's really a number of things it messes up, even on flat land. I also just find it annoying if my empire is split over the seam. You're right of course that the true solution is to shift the axis to the "wettest" part of the map though.
     
  2. Bobert13

    Bobert13 Prince

    Joined:
    Feb 25, 2013
    Messages:
    345
    It took me 6 or 7 hours, (2 of which were probably wasted on trying to figure the right offset to get it to actually line the water tiles up properly :crazyeye:) but I got it working.

    If you'd like to try it out, replace function GeneratePlotTypes() with this entire section of code:
    Code:
    -------------------------------------------------------------------------------------------
    --ShiftMap Class
    -------------------------------------------------------------------------------------------
    function ShiftMaps()
    	--local stripRadius = self.stripRadius;
    	local shift_x = 0; 
    	local shift_y = 0;
    
    	shift_x = DetermineXShift();
    	
    	ShiftMapsBy(shift_x, shift_y);
    end
    -------------------------------------------------------------------------------------------	
    function ShiftMapsBy(xshift, yshift)	
    	local W, H = Map.GetGridSize();
    	if(xshift > 0 or yshift > 0) then
    		local Shift = {}
    		local iDestI = 0
    		for iDestY = 0, H-1 do
    			for iDestX = 0, W-1 do
    				local iSourceX = (iDestX + xshift) % W;
    				
    				--local iSourceY = (iDestY + yshift) % H; -- If using yshift, enable this and comment out the faster line below. - Bobert13
    				local iSourceY = iDestY
    				
    				local iSourceI = W * iSourceY + iSourceX
    				Shift[iDestI] = elevationMap.data[iSourceI]
    				--print(string.format("Shift:%d,	%f	|	eMap:%d,	%f",iDestI,Shift[iDestI],iSourceI,elevationMap.data[iSourceI]))
    				iDestI = iDestI + 1
    			end
    		end
    		elevationMap.data = Shift --It's faster to do one large table operation here than it is to do thousands of small operations to set up a copy of the input table at the beginning. -Bobert13
    	end
    	return elevationMap
    end
    -------------------------------------------------------------------------------------------
    function DetermineXShift()
    	--[[ This function will align the most water-heavy vertical portion of the map with the 
    	vertical map edge. This is a form of centering the landmasses, but it emphasizes the
    	edge not the middle. If there are columns completely empty of land, these will tend to
    	be chosen as the new map edge, but it is possible for a narrow column between two large 
    	continents to be passed over in favor of the thinnest section of a continent, because
    	the operation looks at a group of columns not just a single column, then picks the 
    	center of the most water heavy group of columns to be the new vertical map edge. ]]--
    
    	-- First loop through the map columns and record land plots in each column.
    	local gridWidth, gridHeight = Map.GetGridSize();
    	local land_totals = {};
    	for x = 0, gridWidth - 1 do
    		local current_column = 0;
    		for y = 0, gridHeight - 1 do
    			local i = y * gridWidth + x + 1;
    			if not elevationMap:IsBelowSeaLevel(x,y) then
    				current_column = current_column + 1;
    			end
    		end
    		table.insert(land_totals, current_column);
    	end
    	
    	-- Now evaluate column groups, each record applying to the center column of the group.
    	local column_groups = {};
    	-- Determine the group size in relation to map width.
    	local group_radius = 3;
    	-- Measure the groups.
    	for column_index = 1, gridWidth do
    		local current_group_total = 0;
    		--for current_column = column_index - group_radius, column_index + group_radius do
    		--Changed how group_radius works to get groups of four. -Bobert13
    		for current_column = column_index, column_index + group_radius do
    			local current_index = current_column % gridWidth;
    			if current_index == 0 then -- Modulo of the last column will be zero; this repairs the issue.
    				current_index = gridWidth;
    			end
    			current_group_total = current_group_total + land_totals[current_index];
    		end
    		table.insert(column_groups, current_group_total);
    	end
    	
    	-- Identify the group with the least amount of land in it.
    	local best_value = gridHeight * (group_radius + 1); -- Set initial value to max possible.
    	local best_group = 1; -- Set initial best group as current map edge.
    	for column_index, group_land_plots in ipairs(column_groups) do
    		if group_land_plots < best_value then
    			best_value = group_land_plots;
    			best_group = column_index;
    		end
    	end
    	
    	-- Determine X Shift	
    	local x_shift = best_group + 2;
    
    	return x_shift;
    end
    ------------------------------------------------------------------------------
    --DiffMap Class
    ------------------------------------------------------------------------------
    --Seperated this from GeneratePlotTypes() to use it in other functions. -Bobert13
    
    DiffMap = inheritsFrom(FloatMap)
    
    function GenerateDiffMap(width,height,xWrap,yWrap)
    	DiffMap = FloatMap:New(width,height,xWrap,yWrap)
    	local i = 0
    	for y = 0, height - 1,1 do
    		for x = 0,width - 1,1 do
    			if elevationMap:IsBelowSeaLevel(x,y) then
    				DiffMap.data[i] = 0.0
    			else
    				DiffMap.data[i] = GetDifferenceAroundHex(x,y)
    			end
    			i=i+1
    		end
    	end
    
    	DiffMap:Normalize()
    	i = 0
    	for y = 0, height - 1,1 do
    		for x = 0,width - 1,1 do
    			if elevationMap:IsBelowSeaLevel(x,y) then
    				DiffMap.data[i] = 0.0
    			else
    				DiffMap.data[i] = DiffMap.data[i] + elevationMap.data[i] * 1.1
    			end
    			i=i+1
    		end
    	end
    
    	DiffMap:Normalize()
    	return DiffMap
    end
    -------------------------------------------------------------------------------------------
    function GeneratePlotTypes()
    	print("Creating initial map data - PerfectWorld3")
    	local gridWidth, gridHeight = Map.GetGridSize();
    	--first do all the preliminary calculations in this function
    	print(string.format("map size: width=%d, height=%d",gridWidth,gridHeight))
    	mc = MapConstants:New()
    	PWRandSeed()
    
    	elevationMap = GenerateElevationMap(gridWidth,gridHeight,true,false)
    	--elevationMap:Save("elevationMap.csv")
    	FillInLakes()
    
    	--now gen plot types
    	print("Generating plot types - PerfectWorld3")
    	ShiftMaps();
    
    	DiffMap = GenerateDiffMap(gridWidth,gridHeight,true,false);
    	rainfallMap, temperatureMap = GenerateRainfallMap(elevationMap)
    	--rainfallMap:Save("rainfallMap.csv")
    
    	riverMap = RiverMap:New(elevationMap)
    	riverMap:SetJunctionAltitudes()
    	riverMap:SiltifyLakes()
    	riverMap:SetFlowDestinations()
    	riverMap:SetRiverSizes(rainfallMap)
    
    	--find exact thresholds
    	local hillsThreshold = DiffMap:FindThresholdFromPercent(mc.hillsPercent,false,true)
    	local mountainsThreshold = DiffMap:FindThresholdFromPercent(mc.mountainsPercent,false,true)
    	local i = 0
    	for y = 0, gridHeight - 1,1 do
    		for x = 0,gridWidth - 1,1 do
    			local plot = Map.GetPlot(x,y);
    			if elevationMap:IsBelowSeaLevel(x,y) then
    				plot:SetPlotType(PlotTypes.PLOT_OCEAN, false, false)
    			elseif DiffMap.data[i] < hillsThreshold then
    				plot:SetPlotType(PlotTypes.PLOT_LAND,false,false)
    			--This code makes the game only ever plot flat land if it's within two tiles of 
    			--the seam. This prevents issues with tiles that don't look like what they are.
    			elseif x == 0 or x == 1 or x == gridWidth - 1 or x == gridWidth -2 then
    				plot:SetPlotType(PlotTypes.PLOT_LAND,false,false)
    			-- Bobert13
    			elseif DiffMap.data[i] < mountainsThreshold then
    				plot:SetPlotType(PlotTypes.PLOT_HILLS,false,false)
    			else
    				plot:SetPlotType(PlotTypes.PLOT_MOUNTAIN,false,false)
    			end
    			i=i+1
    		end
    	end
    	GenerateCoasts();
    end
    ------------------------------------------------------------------------------
    This includes my global DiffMap (though it didn't need to as all I had to do was shift the elevationMap at the right time and it shifts everything :D) and I left the old fix in to even further reduce the chances of bad tiles. If you play Wrapping Pangeas or something that is always going to have a lot of land on the seam regardless of the shift, feel free to comment out the two lines surrounded by signed comments near the bottom of my modified GeneratePlotTypes().

    Edit (3/4/2012): Replaced the code with a faster version. I completely got rid of the PlotMap and just used the ElevationMap itself to determine where the wettest part of the map is.

    Edit (3/5/2012): Fixed a bug inherent in function ShiftMapsBy() and made it even faster! Sped up index determination by keeping track of what iteration the script is on instead of calculating the index for x,y every iteration. Also sped up the map shift by getting rid of the copied table, moving the data from the original to a buffer table then setting the buffer as the new table.
     
  3. SlightlyMad

    SlightlyMad Prince

    Joined:
    Sep 26, 2007
    Messages:
    476
    Location:
    Baltimore, MD
    Holy cow! Thanks Bobert, I'll try this out next time I play, maybe Sunday!
     
  4. Bobert13

    Bobert13 Prince

    Joined:
    Feb 25, 2013
    Messages:
    345
    The default latitude determination plots the equator slightly north (on the row of tiles above the center seam instead of on the center seam). As is, this doesn't create any problems with the map, it's just a nitpick; however, it can lead to some strange results if you start messing around with the weather system. :cool: My reworked version balances the hemispheres with the equator resting comfortably on the center seam.

    To implement this, replace function FloatMap:GetLatitudeForY with:
    Code:
    function FloatMap:GetLatitudeForY(y)
    	local range = mc.topLatitude - mc.bottomLatitude
    	local lat = nil
    	if y < self.height/2 then
    		lat = (y+1) / self.height * range + (mc.bottomLatitude - mc.topLatitude / self.height)
    	else
    		lat = y / self.height * range + (mc.bottomLatitude + mc.topLatitude / self.height)
    	end
    	return lat
    end
    A lot of users of this script may have noticed the blatant band of marsh/jungle on the equator. Literally a two tile wide band circumnavigating the globe. To fix this, I implemented a new MainConstant (so it's tweakable) that somewhat normalizes rainfall at the highest and lowest rainfall areas of the map. This alteration requires the fix for latitude determination or you will end up with a one-tile-wide band of dry land (plains and desert) just north of the equator.

    First, add the following line anywhere in the MainConstants section of the script (up at the top). I personally put it with the crazy rain tweaking constants, because that's where I feel it belongs, but it really doesn't matter.:
    Code:
    mconst.pressureNorm = 0.9 --[1.0 = no normalization] Helps to prevent exaggerated Jungle/Marsh banding on the equator. -Bobert13
    Second, alter function FloatMap:GetGeostrophicPressure like so:
    Code:
    function FloatMap:GetGeostrophicPressure(lat)
    	local latRange = nil
    	local latPercent = nil
    	local pressure = nil
    	if lat > mc.polarFrontLatitude then
    		latRange = 90.0 - mc.polarFrontLatitude
    		latPercent = (lat - mc.polarFrontLatitude)/latRange
    		pressure = 1.0 - latPercent
    	elseif lat >= mc.horseLatitudes then
    		latRange = mc.polarFrontLatitude - mc.horseLatitudes
    		latPercent = (lat - mc.horseLatitudes)/latRange
    		pressure = latPercent
    	elseif lat >= 0.0 then
    		latRange = mc.horseLatitudes - 0.0
    		latPercent = (lat - 0.0)/latRange
    		pressure = 1.0 - latPercent
    	elseif lat > -mc.horseLatitudes then
    		latRange = 0.0 + mc.horseLatitudes
    		latPercent = (lat + mc.horseLatitudes)/latRange
    		pressure = latPercent
    	elseif lat >= -mc.polarFrontLatitude then
    		latRange = -mc.horseLatitudes + mc.polarFrontLatitude
    		latPercent = (lat + mc.polarFrontLatitude)/latRange
    		pressure = 1.0 - latPercent
    	else
    		latRange = -mc.polarFrontLatitude + 90.0
    		latPercent = (lat + 90)/latRange
    		pressure = latPercent
    	end
    [COLOR="SeaGreen"]-- Prevents excessively high and low pressures which helps distribute rain more evenly in the affected areas. -Bobert13[/COLOR]
    	pressure = pressure + 1
    	if pressure > 1.5 then
    		pressure = pressure * mc.pressureNorm
    	else
    		pressure = pressure / mc.pressureNorm
    	end
    	pressure = pressure - 1
    [COLOR="SeaGreen"]-- FIN -Bobert13[/COLOR]
    	--print(pressure)
    	return pressure
    end
    And last but not least, a random seed fix that works for Multiplayer and doesn't break Singleplayer randomization:
    Code:
    function PWRandSeed(fixedseed)
    	local seed
    	if fixedseed == nil then
    		[COLOR="SeaGreen"]seed = Map.Rand(2147483647,"") --This function caps at this number, if you set it any higher, or try to trick it with multiple RNGs that end up with a value above this, it will break randomization. This is 31 bits of precision so... - Bobert13[/COLOR]
    	else
    		seed = fixedseed
    	end
    	math.randomseed(seed)
    	print("random seed for this map is " .. seed)
    end
    For Multiplayer support you obviously need to set "SupportsMultiplayer" to true as well as altering the above. I don't know if it's a Civ Limitation, a C++ Limitation or an LUA limitation but if you attempt to set a seed that requires more than 31 bits to store, it does break randomization. If you don't believe me, change 2147483647 to 2147483648 in the above and load up a game. Reveal the map, rinse and repeat. I'd suggest doing this on the duel Map Size to save yourself time, but any size will work. Even though you keep spawning in a new location and resources are placed differently, you'll notice every time you reveal the map, it's the exact same map as last time...
     
  5. Bobert13

    Bobert13 Prince

    Joined:
    Feb 25, 2013
    Messages:
    345
    Does anyone happen to know what exactly Cephalo used to view the .csv files he saved?

    I'm currently working on plate tectonics and it would be really nice to see the .csv file I'm saving in something that takes the hex grid into account. It'd be even better if it could handle coloring every different plate a different color or something similar (I've got a table of values and each plate has a distinct value). Viewing them in OpenOffice works (kindof) but it's not great...
     
  6. NBAfan

    NBAfan boss

    Joined:
    Aug 30, 2007
    Messages:
    3,351
    Use Notepad ++
     
  7. Bobert13

    Bobert13 Prince

    Joined:
    Feb 25, 2013
    Messages:
    345
    After a while of tinkering with how I was making the .csv's (I added line breaks at the end of each row, etc.) and tinkering with OpenOffice Calc, I was able to get a really nice overview of my PlateMap. It took some work in Calc (too bad I'm not familiar with writing Macros...) but I was eventually able to get really really close to what I'd wanted. Here's an example image of one randomly generated Huge (128 x 80) PlateMap after I colored in the plates and such:


    It still doesn't take the Hex grid into account, but it was clear enough for what I needed to see.
     
  8. Puupertti Ruma

    Puupertti Ruma Chieftain

    Joined:
    Nov 29, 2004
    Messages:
    5
    Location:
    Finland
    I have a RPG world which I've been doing for a while now. I was hoping to use Perfectworld 3's various simulations to calculate biomes and such for my world. What I'm trying to get is in other words about the same what Bobert13 got in his post above mine. A nice maps full of information, not civ V maps. What I'dd want to do is build the elevation.csv myself and then let the awesome scripts do the climate modeling and such, but I'm really strugling. I can't, for starters, get the script to do the .csv's or actually I think it crashes at the start when I try to run it with lua for windows. Could someone help me? For example pointing the relevant functions that generate and load the .csv's used...

    I have some experience on java, so I'm not a total noob with programming languages, but lua is totally new for me.
     
  9. Bobert13

    Bobert13 Prince

    Joined:
    Feb 25, 2013
    Messages:
    345
    I couldn't get the script to run through LUA for Windows either as it got stuck on the Includes at the very top of the file, but it was still useful as a general debug/syntax checker. For map previews and .csv saves I just used Worldbuilder as it parses the LUA much quicker than the game itself.

    All of the lines that call the save function {function FloatMap:Save()} are commented out. It however saves the .csv in a one dimensional format (a single long string of numbers). It does include the Width and Height of the map as the first two data entries so I suspect Cephalo had some way to convert the 1D strings into 2D maps.

    I personally handled my .csv files by creating a more complex FloatMap:Save2(). It includes linebreaks and a row and column dedicated to the x and y coordinates. It also resolves the y coordinate inversely so that it doesn't print out a flipped version of the map(0,0 is in the bottom left corner of the map, not the top left). To call it, you must be storing your elevation map at name.data of a FloatMap (not just a normal table). Additionally, your FloatMap must inherit or calculate the width and height of the map and store these at name.width and name.height respectively.

    Code:
    function FloatMap:Save2(name)
    	local file = io.open(name,"w+")
    	local first = true
    	local str = ""
    	for y = self.height, 0, -1 do
    		if first then
    			str = "xy,"
    		else
    			str = string.format("%d,",y)
    		end
    		for x = 0, self.width-1 do
    			local i = y*self.width+(x%self.width)
    			if first then
    				if x < self.width-1 then
    					str = str..string.format("%d,",x)
    				else
    					str = str..string.format("%d\n",x)
    				end
    			elseif x < self.width-1 then
    				str = str..string.format("%.1f,",self.data[i])
    			else
    				str = str..string.format("%.1f\n",self.data[i])
    			end
    		end
    		first = false
    		file:write(str)
    	end
    	file:close()
    	print("bitmap saved as"..name..".")
    end
    The files this function saves are immediately viewable in Excel/OpenOffice Calc.

    The line to call it should look like:
    Code:
    [COLOR="Blue"]YourFloatMap[/COLOR]:Save2("[COLOR="Blue"]YourFileName[/COLOR].csv")
    It saves these .csv files to the root Civ V directory.

    Edit(again): The number of decimal places to save can be configured. Currently it's set to only store one decimal with "%.1f". Changing the 1 to a 4 for example would store out to 4 decimal places. Removing .1 so that it looks like "%f" stores the default number of decimal places which I believe is 8 but I'm not 100% sure. "\n" is the linebreak.
     
  10. Argalis

    Argalis Chieftain

    Joined:
    Apr 19, 2013
    Messages:
    4
    Bobert, you couldn't possibly upload a map script of perfect world with your changes applied to it for those of us less technically able? I've had a look around at the lua file on notepad, but ended up a wee bit confused resulting in some corrupted files...

    Thanks if you can, if not don't worry, I'll probably be able to figure out exactly where to put it in the end!
     
  11. Bobert13

    Bobert13 Prince

    Joined:
    Feb 25, 2013
    Messages:
    345
    I'll look into this over next week. I've been messing around with other games lately and such, so my tectonics implementation has been on hold.

    My last working version has no tectonics alterations but I've tweaked it to the point that it doesn't really feel like Perfect World 3, which would be my biggest obstacle in releasing an "update". I'll look at cleaning it up and reverting some of my Main Constant tweaks to see if I can't maybe get a "3.1" that feels true to 3 and incorporates some of my optimizations and the commonly requested tweaks (oasis placement, xShift, and such).
     
  12. mrrar

    mrrar Chieftain

    Joined:
    Nov 25, 2011
    Messages:
    2
  13. Seabastian Civ

    Seabastian Civ King

    Joined:
    Sep 10, 2012
    Messages:
    608
    This script is amazing. Thank you so much.
     
  14. Cajamarca

    Cajamarca Chieftain

    Joined:
    Jul 10, 2012
    Messages:
    48
    O, Bobert13, where art thou?
     
  15. RRtexasranger

    RRtexasranger Warlord

    Joined:
    Dec 8, 2007
    Messages:
    148
    You can play it in multiplayer (playing with it right now), but both players need to have the map enabled. I just moved the LUA file into the C:\Program Files (x86)\Steam\steamapps\common\sid meier's civilization v\Assets\Maps folder. And then open the LUA, find where it says "SupportsMultiplayer" and change it to "true" instead of "false".

    If both players do this, it will show up as an option for a map type. But I don't remember if legendary start is an option or not.
     
  16. mrtauntaun

    mrtauntaun Chieftain

    Joined:
    Jan 29, 2009
    Messages:
    25
    I use this script in MP all the time. All I had to do is flip the enable MP flag to true. The only problem we have is when we first start, the game will restart after turn 1 and any players > player 1 will find themselves in another spot on the map.

    Legendary start is an option.
     
  17. croatian_pig

    croatian_pig Chieftain

    Joined:
    May 12, 2013
    Messages:
    1
    Hi guys, I'm having trouble installing this mod and enabling it.

    Do I have to unpack with winrar only core file and put those 2 files in "mods" folder, or I need to use some program to unpack each of them again after winrar?

    I enabled mode "Perfect world" in Civ5 Gods and Kings, but I don't see the options when I look at map types (amazon plus, continents, pangea, .....).


    P.S. I don't understand what or how to activate mapscript. I started normal Civ5 G&K DX11 in my folder.

    .......
    Solved.........
     
  18. mike3640

    mike3640 Chieftain

    Joined:
    May 11, 2009
    Messages:
    42
    Location:
    new york
    Hi guys I was wondering how I can edit this map script to produce less mountains? Currently the amount of mountain ranges kinda breaks the game and makes too much land unusable. Thanks!
     
  19. PawelS

    PawelS Ancient Druid

    Joined:
    Dec 11, 2003
    Messages:
    2,803
    Location:
    Poland
    At the beginning of the code, there are some parameters that you can edit. To make the script produce less mountains, increase mconst.mountainsPercent (it's the percent of land that is not mountains).
     
  20. mike3640

    mike3640 Chieftain

    Joined:
    May 11, 2009
    Messages:
    42
    Location:
    new york
    Thank you. I love the map script but last time I played mountains destroyed my starting position.
     

Share This Page