Ongoing Multi-Map Development Discussion

Thunderbrd

C2C War Dog
Joined
Jan 2, 2010
Messages
29,813
Location
Las Vegas
This post is to provide modders with an overview of the current support for multi-maps and viewports.

Overview

Firstly a brief recap of the traditional, vanilla Civ IV structure:

Each game state (either from a save, or generated when starting a new game) includes a single map, represented by the CvMap class (CyMap in Python). This class has properties of x and y size, and how the coordinates wrap (so plane, vs torus vs sphere topologically). It also allows you to find the details of any particular plot given its coordinates (returns a CvPlot or CyPlot)

From the perspective of the game engine, over which we have no control, the above model must be what the game engine sees. Hence, to change what gets displayed all we can do is vary the CONTENTS of the single map that is presented to the game engine (i.e. - what's in the plots). To achieve this we introduce a proxy class which presents the same external interface as CvMap, but pulls its data from different places (maybe different underlying maps, maybe different regions of a single map). This proxy is what is known as a viewport (CvViewport) [actually the class hierarchy is more complex than this, but I'll go into fuller detail later]. To switch maps we just change the CvMap the viewport pulls its data from and tell the game engine that all plots are dirty, so it requeries everything. Python does not see the viewport directly, since currently it always operates on the complete map (unlike the game engine it has to process game state, not just passively display things, so it has to see everything). For that reason Python access to the map bypasses the viewport and always accesses the underlying map. There are some exposed functions Python can use however, to determine whether a particular plot is in the current viewport that the game engine is seeing.

This system is most developed to support user-visible viewports as windows into a single larger map, but it's fundamentally exactly the same as viewports onto multiple maps. In current C2C it is always active, even if you have (user level) viewports turned off. In that default case the viewport simply has the same dimensions as the entire map, so the fact that the game engine is interfacing via the viewport has no functional effect.

C2C also supports (to some extent) multiple actual maps. The globals (CvInternalGlobals) holds an array of maps (instead of the single one that vanilla BTS holds). At any time any one of these can be the 'current' map (and for now map index 0 is always current until we add UI and Python to switch). The current map can be switched using cvInternalGlobals::switchMap() to any in the allocated array. Currently, when processing end turn, only the current map will be processed (so in effect the turn only takes place on the current map) - this is one of the things that will have to change (it will need to iterate through all the maps playing the [AI] turn in each).

At game start map 0 is automatically generated. At any other time new maps (with indexes > 0) may be generated by Python calls. Such map generation just creates a new CvMap instance - it does not actually generate its plots at the time. The first time a map switch is made to a new map its plots will be generated. Currently this just uses the default map generator, and there is no control over map script used (this needs to change - see below)

We also inherited (from Lytning's parallel maps prototype) some map-definition XML, though this is essentially unused by C2C. It is accessible via Python. It consists of two files:

CvMapInfo.xml, which defines a set of maps; and
CvMapSwitchInfo.xml which defines mechanisms for switching between maps

Both of these are essentially unused collections of properties. Lytning's intent was to access these from Python, and based on what they contain, to call the other Python APIs that actually manipulate and switch between multimaps, to generate (and use) the necessary resulting maps.

My recommendation is to do away entirely with CvMapSwitchInfo (map switching is better handled as implicit mechanisms in the DLL and UI elements via the Python IMO). I would also repurpose (and enhance) CvMapInfo so that it defines the full set of maps available (including scripts etc.). In effect that means that it predefines a set of types (MapTypes, which is an enum we already have) which are valid indexes to use when calling functions like SetCurrentMap(). The DLL needs to be enhanced to read the appropriate map script (and other properties like size and wrapping) when it needs to generate the map on demand the first time it is switched to (that way we don't take the memory hit until the map is needed). The code that does this (and will need to be enhanced to read CvMapInfo and act accordingly) is in CvMap::afterSwitch().

Detailed reference material

In detail the actual class hierarchy used for maps is as follows:

CvMapInterfaceBase - abstract class that defines the external interface a map needs to provide

CvMap - underlying concrete map - a subclass of CvMapInterfaceBase

CvViewport - a window onto an underlying map - a subclass of CvMapInterafceBase. This has a reference to a CvMap instance which is the udnerlying map - the viewport size may be any size less than or equal to the underlying map size

CvMapExternal - class the game engine links to. This is just a shell that forwards all requests to an associated CvMpInterfaceBase instance, which will be the current viewport on the current map

Python methods are currently exposed on CyMap, and are as follows:

Map type (so this will correspond with the type id in CvMapInfo.xml if we follow my suggestion above - currently it's just an array index in the global map array which is pretty meaningless to Python). If we do this then the setType() method should be removed from the python, since it will only be legitimate to set the type at map creation type.
Code:
	int getType();
	void setType(int iNewType);

Viewports:
Code:
	bool viewportsEnabled();
	int	getViewportWidth();
	int	getViewportHeight();
	int	getMapXFromViewportX(int iX);
	int	getMapYFromViewportY(int iY);
	int	getViewportXFromMapX(int iX);
	int	getViewportYFromMapY(int iY);
	bool isInViewport(int X, int Y);

Viewport manipulation (don't mess with closeAdvsor unless you know what you're doing - it's used to switch back to a normal view after being in the military advisor):
Code:
	void closeAdvisor(int advisorWidth, int iMinimapLeft, int iMinimapRight, int iMinimapTop, int iMinimapBottom);
	void bringIntoView(int iX, int iY, bool bDisplayCityScreen);

Map switching:

Code:
void CyGlobalContext::switchMap(int iMap);
int CyGlobalContext::getNumMapInfos() const;
CvMapInfo* CyGlobalContext::getMapInfo(int iMap) const;
int CyGlobalContext::getNumMapSwitchInfos() const;
CvMapSwitchInfo* CyGlobalContext::getMapSwitchInfo(int iMapSwitch) const;
CyMap* CyGlobalContext::getMapByIndex(int iIndex);
void CyGlobalContext::initializeMap(int iMap);

These are only partially hooked up currently, and if we follow my proposal of getting rid of CvMapSwitchInfo.xml and using CvMapInfo.xml to define all the legitimate maps (MAP_EARTH, MAP_MOON, ...) then getMapByIndex() and getMapSwitchInfo() will disappear. InitializeMap() is also not needed in the Python, since we'll just have the DLL initialize all maps at startup (note - initializing a map is not the same as generating it, which happens on demand when it is first switched to)

Koshling, this is so amazingly helpful its unreal! I have only skimmed at this point due to some other more pressing matters to attend to at the moment but I wanted to start a thread based on your post so that we don't fill up the Modder's Documentation thread because I can already see I'm going to have some more questions and we have some work to do as a group to plan out where to go from here. But all in all I believe with this one post you may have just enabled me to, with patience, carry this out to a point of further completion. I have a lot of very pressing goals for this version but at the point in time I'm ready to move forward here what you've done to set us up is just... awesome!

Yes, like yourself, some help in designing the map generation routines themselves may be the biggest gap I'd want some assistance with. These would have to be considered one map TYPE at a time. Anyhow, I'll comment more as soon as I can devour this explanation in much greater depth but again, I think I speak for the whole team when I say, thank you!
 
From what I can tell, the maps would be 'designated' as being a part of the game (given an ID basically) at the beginning of the game, which would be necessary such that we may refer to them. But they would only be 'generated', as in fully defined with all the plot details and such, once it becomes necessary to do so in the game. Once generated, then they are fully detailed, static and active maps. But until then, they are just an ID number basically.

This means something for the coding aspect, so that we can refer to the map at all in code, but would not mean any slowdown in load times, though there may be a somewhat significant wait while the map is generated in game. But that's a one time hit for each map of course and in a dll based map generation application it probably wouldn't be nearly as bad a delay as the python interfaced mapscript generators we have for the main map. Provided a great coding effort could be made to presetup the mapscripting in dll/xml tag references. Honestly, to me that sounds much easier than fiddling with py mapscripting methods... but that's just me and how little I get along with python.
 
Ok my next question is, would all map have to be the same size? Or could we have say a "Lunar" map a % smaller than the "Earth" map?

The only rule is that the viewport size must be constant (and you cannot have multimaps without viewports). If you elect to have (say) a viewport size of 40X40 (which would remain a user BUG setting as now) that viewport could operate against any of the underlying maps, which can have differing sizes and wrapping characteristics as needed.

From what I can tell, the maps would be 'designated' as being a part of the game (given an ID basically) at the beginning of the game, which would be necessary such that we may refer to them. But they would only be 'generated', as in fully defined with all the plot details and such, once it becomes necessary to do so in the game. Once generated, then they are fully detailed, static and active maps. But until then, they are just an ID number basically.
That is correct

When did Koshling write that info?
Yesterday
 
The only rule is that the viewport size must be constant (and you cannot have multimaps without viewports). If you elect to have (say) a viewport size of 40X40 (which would remain a user BUG setting as now) that viewport could operate against any of the underlying maps, which can have differing sizes and wrapping characteristics as needed.
That is correct
Yesterday

Question for ya (and if dumb, oh well, thats just me) anyways, in 1986, it had a over-world and a under-world, just wondering if this is possible, or an i off base here??

[offtopic]In anycase, i just got my Brand new 65" 3D ready tv from Samsung, Smart TV, so i got to let the movers upstairs where i live. later . . . SO,, Now to get a newer PC (I wish)
 
Question for ya (and if dumb, oh well, thats just me) anyways, in 1986, it had a over-world and a under-world, just wondering if this is possible, or an i off base here??

[offtopic]In anycase, i just got my Brand new 65" 3D ready tv from Samsung, Smart TV, so i got to let the movers upstairs where i live. later . . . SO,, Now to get a newer PC (I wish)

It's entirely possible - you'd just need a special map script that generated one constrained by the other so they 'matched up'
 
Well, this should be very helpful. I think that I'll spend some time trying to implement the 'guts' of this, then pass it on to AIAndy or Thunderbrd for the Map generation and Koshling for the AI.

Edit:

Also, what do you think the first thing to be done with this should be? I'm not certain where to start (but am leaning towards junking the mapswitch infos and redoing mapinfos to have much more info).
 
The only rule is that the viewport size must be constant (and you cannot have multimaps without viewports). If you elect to have (say) a viewport size of 40X40 (which would remain a user BUG setting as now) that viewport could operate against any of the underlying maps, which can have differing sizes and wrapping characteristics as needed.

Yeah I was not asking viewport size. I realize that the viewport stays the same size.

Anywho that's great news that we could have say the Moon map be 1/4 the size of the Earth map and the Mars map be 1/2 the size of the Earth map that is chosen.

The only bad part I suppose would be if someone chose a duel size map and thus Mars and the Moon would have to be smaller. However I suppose in this case we could have them all the same size if you cannt make maps smaller.
 
Yeah I was not asking viewport size. I realize that the viewport stays the same size.

Anywho that's great news that we could have say the Moon map be 1/4 the size of the Earth map and the Mars map be 1/2 the size of the Earth map that is chosen.

The only bad part I suppose would be if someone chose a duel size map and thus Mars and the Moon would have to be smaller. However I suppose in this case we could have them all the same size if you cannt make maps smaller.

Another thing is that you can't make the Viewport bigger than the underlying map, otherwise bad things start to happen.
 
One thing that is needed for Earth maps is something that identifies where the zero meridian is. Otherwise we are going to have to have specific spawns for each map built. EG GEM has its own spawn Infos file because its zero meridian is 150 degrees west of the default zero meridian that the spawn code uses.
 
Could the Viewport be auto-reset when switching to a smaller map? Have it store and recall the largest size setting being utilized by the player and when going to another map use the largest possible setting but resizing it to the smaller sizes if the map requires it?
 
Could the Viewport be auto-reset when switching to a smaller map? Have it store and recall the largest size setting being utilized by the player and when going to another map use the largest possible setting but resizing it to the smaller sizes if the map requires it?

It is impossible to change viewport size without closing and restarting the game. The viewport is what the game engine sees as being the map, and the game engine has no concept of the map changing (all we can do is change is contents, thus the viewport size cannot change under the instance of the game engine it is running against.

However, a viewport larger than the underlying map can be made to work (if it doesn't already), with the 'spare' tiles just showing up as blank tiles you cannot move into.
 
:bump: for DH. Since you're looking into this perhaps some of this will make more sense to you. I know there's probably some buggy spots in the code that have been due to unfamiliarity with this stuff since it was initially written but I don't think they'll be too tough to uproot.

I still haven't had much time to work with this at all and it's really on the 'after a ton of other stuff' list for me.
 
I forgot about the whole mini map problem :(

If the mini map is a problem, maybe we dont use it on the 2nd map, it will only be in use on the original map, thats the -bonus of using multi map, i guess??
 
Top Bottom