PerfectWorld3

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.
 
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.

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.
 
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...
 
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...
 
Use Notepad ++

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.
 
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.
 
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.
 
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!
 
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!

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).
 
This script is amazing. Thank you so much.
 
I'm looking to play this in Multiplayer

Is the following post still valid?
http://forums.civfanatics.com/showpost.php?p=10731802&postcount=277

Also I play with my wife, and we like to use Legendary Start because we're pretty casual players and just like to play together :) . Is that an option in this map?

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.
 
I'm looking to play this in Multiplayer

Is the following post still valid?
http://forums.civfanatics.com/showpost.php?p=10731802&postcount=277

Also I play with my wife, and we like to use Legendary Start because we're pretty casual players and just like to play together :) . Is that an option in this map?

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.
 
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.........
 
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!
 
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!

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).
 
Top Bottom