HEX Mod (discontinued)

PieceOfMind

Drill IV Defender
Retired Moderator
Joined
Jan 15, 2006
Messages
9,319
Location
Australia


(discontinued)

Prologue
Spoiler :

With the news that civ5 will be requiring us to make a transition from squares to hexagons (aka hexes) as our preferred unit of gameboard, there has been much discussion on how these new shapes will behave. Some have even wondered so much the pesky hexes have invaded their dreams, tormenting their civ4-playing souls. "How", they wonder, "could civ4 be imagined in this new geometry?"

With this in mind, I decided to embark on a journey of epic proportions. I took the first step towards creating a love-child of hexes and squares, in the hope that civ4 could borrow some of the genius of the tactically superior hex formation. This was a messy process and not pleasant for the victim square who went under the knife. Two of its opposite corners were cut clean off, proving the point to all the other squares on the grid that it was possible for them too to become hexes. Packed like sardines, these artificial hexes were only partly relieved to find they now only touched 6 other units and noted that it was at least better than being crammed to the point where they would be touching 8 other units.

Meanwhile the omniscient began to play civ on their mangled bodies. :D


Now that I've proven I've no writing talent, why don't we get straight to the good bit: The HEX Mod.

In this mod we mess with one of the most fundamental rules of the game - adjacency. Currently all tiles on the board, apart from those at the poles, neighbour 8 other tiles because they are all packed as squares. By changing the adjacency rules slightly, by banning one of the diagonal directions for movement (among other things) we can functionally duplicate a true hex board.

Now, my progress so far has been sketchy. My first attempt allowed a successful compile but this is how it looked in game - clearly I broke it! :eek:


After fixing a few obvious and careless mistakes/bugs, I obtained:


Also getting infinite loops, almost certainly because I've mucked up something with a loop through NUM_DIRECTION_TYPES.



Here's how you use a square gameboard to play on hexes:
(each number is the distance from the red 0.
Code:
            // 3 | 3 | 3 | 3 | 4 | 5 | 6
           // -------------------------
          // 3 | [b]2[/b] | [b]2[/b] | [b]2[/b] | 3 | 4 | 5
         // -------------------------
	// 3 | [b]2[/b] | [color="blue"]1[/color] | [color="blue"]1[/color] | [b]2[/b] | 3 | 4
       // -------------------------
      // 3 | [b]2[/b] | [color="blue"]1[/color] | [color="red"]0[/color] | [color="blue"]1[/color] | [b]2[/b] | 3
     // -------------------------
    // 4 | 3 | [b]2[/b] | [color="blue"]1[/color] | [color="blue"]1[/color] | [b]2[/b] | 3
   // -------------------------
  // 5 | 4 | 3 | [b]2[/b] | [b]2[/b] | [b]2[/b] | 3
 // -------------------------
// 6 | 5 | 4 | 3 | 3 | 3 | 3


// 3 | 3 | 3 | 3 | 4 | 5 | 6
// -------------------------
// 3 | [b]2[/b] | [b]2[/b] | [b]2[/b] | 3 | 4 | 5
// -------------------------
// 3 | [b]2[/b] | [color="blue"]1[/color] | [color="blue"]1[/color] | [b]2[/b] | 3 | 4
// -------------------------
// 3 | [b]2[/b] | [color="blue"]1[/color] | [color="red"]0[/color] | [color="blue"]1[/color] | [b]2[/b] | 3
// -------------------------
// 4 | 3 | [b]2[/b] | [color="blue"]1[/color] | [color="blue"]1[/color] | [b]2[/b] | 3
// -------------------------
// 5 | 4 | 3 | [b]2[/b] | [b]2[/b] | [b]2[/b] | 3
// -------------------------
// 6 | 5 | 4 | 3 | 3 | 3 | 3

Here is the picture from earlier mangled to try and show the hex nature:



This distance function does the job, and is enough to get the cultural expansions of cities looking correct. It is in CvGameCoreUtils.cpp
Code:
inline int plotDistance(int iX1, int iY1, int iX2, int iY2)													// Exposed to Python
{
	int iDX;
	int iDY;
	
	iDX = iX2 - iX1;
	iDY = iY2 - iY1;
	int sizeX = (iDX<0?-iDX:iDX);
	int sizeY = (iDY<0?-iDY:iDY);

	int iA = std::max(iDX - iDY,iDY - iDX); // distance b/n the two.
	int iM = std::min(sizeX,sizeY);
	
	return std::min(iA + iM,sizeX+sizeY);

}
If anyone can think of a more efficient way to do that calculation I'd love to hear it. A challenge to all you mathematicians/computer scientists out there! :)
It's already pretty speedy, but since this code is called extremely frequently, perfection is preferred.



This is what the above replaces. Note that both plotDistance and stepDistance I plan to replace for the HEX Mod.
Code:
[COLOR="SeaGreen"]// 4 | 4 | 3 | 3 | 3 | 4 | 4
// -------------------------
// 4 | 3 | 2 | 2 | 2 | 3 | 4
// -------------------------
// 3 | 2 | 1 | 1 | 1 | 2 | 3
// -------------------------
// 3 | 2 | 1 | 0 | 1 | 2 | 3
// -------------------------
// 3 | 2 | 1 | 1 | 1 | 2 | 3
// -------------------------
// 4 | 3 | 2 | 2 | 2 | 3 | 4
// -------------------------
// 4 | 4 | 3 | 3 | 3 | 4 | 4
//
// Returns the distance between plots according to the pattern above...[/COLOR]
inline int plotDistance(int iX1, int iY1, int iX2, int iY2)													// Exposed to Python
{
	int iDX;
	int iDY;

	iDX = xDistance(iX1, iX2);
	iDY = yDistance(iY1, iY2);

	return (std::max(iDX, iDY) + (std::min(iDX, iDY) / 2));
}

[COLOR="SeaGreen"]// 3 | 3 | 3 | 3 | 3 | 3 | 3
// -------------------------
// 3 | 2 | 2 | 2 | 2 | 2 | 3
// -------------------------
// 3 | 2 | 1 | 1 | 1 | 2 | 3
// -------------------------
// 3 | 2 | 1 | 0 | 1 | 2 | 3
// -------------------------
// 3 | 2 | 1 | 1 | 1 | 2 | 3
// -------------------------
// 3 | 2 | 2 | 2 | 2 | 2 | 3
// -------------------------
// 3 | 3 | 3 | 3 | 3 | 3 | 3
//
// Returns the distance between plots according to the pattern above...[/COLOR]
inline int stepDistance(int iX1, int iY1, int iX2, int iY2)													// Exposed to Python
{
	return std::max(xDistance(iX1, iX2), yDistance(iY1, iY2));
}


Notes:
Spoiler :
The things the mod should affect:
-Unit movement (not done yet)
-City border expansion (done)
-plus more



Files, locations to examine:
CvGameCoreUtils.h: plotDistance() and stepDistance()
plotDistance is nearly the Euclidean distance. stepDistance is the sort that units would use - diagonalss count as distance 1.
(Change1)


CvMap.cpp: maxStepDistance(), maxPlotDistance()
Uses stepDistance() and plotDistance() but alos makes calls to things like getGridWidthINLINE. Probably needs fixing.
How this will change will depend on how they get used. It's use in CvPlayer.cpp looks the most worrying. It also appears near the end of CvPlayerAI::AI_foundValue.
Whatever change is made, both functions will presumably look identical. It also looks like there's no need to tie it to the (0,0) coordinate. In fact doing so would be wrong.

How will this work if Y wrap is permitted?

(Change2)

...
+Several other points in the code most likely. Some bits could get messy, like looking at how rivers work, and what corners of tiles are.

Notes:
stepDistance() is used in CvGameCoreUtils.cpp in two places, for both of pathHeuristic and stepHeuristic. To my knowledge, both these things are to do with the A* pathfinding algorithm.

(0,0) coordinate is in the bottom left corner of the map.
Hence we make the banned direction be NW and SE (for easier distance calculations I think).


This mod is intended as a bit of fun and should be treated as mostly experimental at this point. I don't have plans to make it very pretty and if it does reach a final stage it will probably have some visual quirks associated with it.

I will likely need some help/advice from some SDK and perhaps Python modders out there. Anyone interested in helping out? It probably won't take a lot of work, luckily.

The first thing I am trying to figure out how to do is to restrict unit movement so that NW and SE directions are banned. If anyone can confirm it's not something locked in the EXE, that would give me peace of mind. :)
 
I think this might be faster for the plot distance function...

Edit: corrected bug - :D

Spoiler :
Code:
[SPOILER]No diagonal travel: distance is sum of the 
distances in the X and Y directions:

//--------------------------
//   |   |   |   | 4 | 5 | 6
// -------------------------
//   |   |   |   | 3 | 4 | 5
// -------------------------
//   |   |   |   | 2 | 3 | 4
// -------------------------
//   |   |   | 0 |   |   |  
// -------------------------
// 4 | 3 | 2 |   |   |   |  
// -------------------------
// 5 | 4 | 3 |   |   |   |  
// -------------------------
// 6 | 5 | 4 |   |   |   |  
//--------------------------

Yes diagonal travel: Distance is the larger of the 
two X and Y distances:

//--------------------------
// 3 | 3 | 3 | 3 |   |   |  
// -------------------------
// 3 | 2 | 2 | 2 |   |   |  
// -------------------------
// 3 | 2 | 1 | 1 |   |   |  
// -------------------------
// 3 | 2 | 1 | 0 | 1 | 2 | 3
// -------------------------
//   |   |   | 1 | 1 | 2 | 3
// -------------------------
//   |   |   | 2 | 2 | 2 | 3
// -------------------------
//   |   |   | 3 | 3 | 3 | 3
//--------------------------
[/SPOILER]

inline int plotDistance(int iX1, int iY1, int iX2, int iY2)
{
    int iDX = iX2 - iX1;
    int iDY = iY2 - iY1;
    int sizeX = (iDX<0?-iDX:iDX);
    int sizeY = (iDY<0?-iDY:iDY);

    if (iDX > 0 && iDY > 0 || iDX < 0 && iDY < 0)

        return sizeX + sizeY;                   [COLOR="SeaGreen"]//No diagonal travel[/COLOR]

    else

        return std::max(sizeX, sizeY);          [COLOR="SeaGreen"]//Yes diagonal travel[/COLOR]
}
 
I think this might be faster for the plot distance function...

Edit: corrected bug - :D

Spoiler :
Code:
[SPOILER]No diagonal travel: distance is sum of the 
distances in the X and Y directions:

//--------------------------
//   |   |   |   | 4 | 5 | 6
// -------------------------
//   |   |   |   | 3 | 4 | 5
// -------------------------
//   |   |   |   | 2 | 3 | 4
// -------------------------
//   |   |   | 0 |   |   |  
// -------------------------
// 4 | 3 | 2 |   |   |   |  
// -------------------------
// 5 | 4 | 3 |   |   |   |  
// -------------------------
// 6 | 5 | 4 |   |   |   |  
//--------------------------

Yes diagonal travel: Distance is the larger of the 
two X and Y distances:

//--------------------------
// 3 | 3 | 3 | 3 |   |   |  
// -------------------------
// 3 | 2 | 2 | 2 |   |   |  
// -------------------------
// 3 | 2 | 1 | 1 |   |   |  
// -------------------------
// 3 | 2 | 1 | 0 | 1 | 2 | 3
// -------------------------
//   |   |   | 1 | 1 | 2 | 3
// -------------------------
//   |   |   | 2 | 2 | 2 | 3
// -------------------------
//   |   |   | 3 | 3 | 3 | 3
//--------------------------
[/SPOILER]

inline int plotDistance(int iX1, int iY1, int iX2, int iY2)
{
    int iDX = iX2 - iX1;
    int iDY = iY2 - iY1;
    int sizeX = (iDX<0?-iDX:iDX);
    int sizeY = (iDY<0?-iDY:iDY);

    if (iDX > 0 && iDY > 0 || iDX < 0 && iDY < 0)

        return sizeX + sizeY;                   [COLOR="SeaGreen"]//No diagonal travel[/COLOR]

    else

        return std::max(sizeX, sizeY);          [COLOR="SeaGreen"]//Yes diagonal travel[/COLOR]
}

That's a nice improvement there. Thanks, I will use it.

@Souron. Hmm, that could work too.

Code:
// 3 | 3 | 3 | 3 | 3 | 3 | 3
// -------------------------
// 4 | [B]2[/B] | [B]2[/B] | [B]2[/B] | [B]2[/B] | [B]2[/B] | 4
// -------------------------
// 5 | 3 | [COLOR="blue"]1[/COLOR] | [COLOR="blue"]1[/COLOR] | [COLOR="blue"]1[/COLOR] | 3 | 5
// -------------------------
// 6 | 4 | [B]2[/B] | [COLOR="Red"]0[/COLOR] | [B]2[/B] | 4 | 6
// -------------------------
// 5 | 3 | [COLOR="Blue"]1[/COLOR] | [COLOR="blue"]1[/COLOR] | [COLOR="blue"]1[/COLOR] | 3 | 5
// -------------------------
// 4 | [B]2[/B] | [B]2[/B] | [B]2[/B] | [B]2[/B] | [B]2[/B] | 4
// -------------------------
// 3 | 3 | 3 | 3 | 3 | 3 | 3
Like that? A circle of radius 2 (i.e. a hexagon) doesn't even look right.
 
Hi J,
Yeah I was thinking too about the limited capacity modcomp and how it might be combined with this.

First step for me though is to get this one working.

My question to modders is this:

At the moment when you want to move a unit you select it and then hold the right mouse button over the destination and the path along with the movement time will be shown to you. What parts of the code would I be looking at in order to ensure that NW and SE moves are disallowed? A unit that wants to go 1NW would have to go one square north and one square west, costing 2 movement points all up.
 
So I've tracked down the pathfinding code to this in CvDLLFAStarIFaceBase.h:
Code:
virtual bool GeneratePath(FAStar*, int iXstart, int iYstart, int iXdest, int iYdest, bool [B]bCardinalOnly [/B]= false, int iInfo = 0, bool bReuse = false) = 0;

Anyone know where to see where that function is worked out? I'm very curious to see what the bCardinalOnly does as it would likely help me with this mod.

I'm fearing it's locked away from us to tamper with it.
 
It will be an implementation of the A Star algorithm, which is pretty straightforward.

bCardinalOnly will only consider NESW moves as valid.

http://en.wikipedia.org/wiki/A*_search_algorithm

Just use your distance metric to the target as the heuristic function (ok since it always underestimates the cost due to terrain costs).

EDIT: It's a pure virtual function so you can just make your own implementation by deriving a class from the interface.

EDIT2: I don't mod myself but I know a great deal about C++ and pathfinding. PM me if you need any help on implemetantion details.
 
@Souron. Hmm, that could work too.

Code:
// 3 | 3 | 3 | 3 | 3 | 3 | 3
// -------------------------
// 4 | [B]2[/B] | [B]2[/B] | [B]2[/B] | [B]2[/B] | [B]2[/B] | 4
// -------------------------
// 5 | 3 | [COLOR="blue"]1[/COLOR] | [COLOR="blue"]1[/COLOR] | [COLOR="blue"]1[/COLOR] | 3 | 5
// -------------------------
// 6 | 4 | [B]2[/B] | [COLOR="Red"]0[/COLOR] | [B]2[/B] | 4 | 6
// -------------------------
// 5 | 3 | [COLOR="Blue"]1[/COLOR] | [COLOR="blue"]1[/COLOR] | [COLOR="blue"]1[/COLOR] | 3 | 5
// -------------------------
// 4 | [B]2[/B] | [B]2[/B] | [B]2[/B] | [B]2[/B] | [B]2[/B] | 4
// -------------------------
// 3 | 3 | 3 | 3 | 3 | 3 | 3
Like that? A circle of radius 2 (i.e. a hexagon) doesn't even look right.

No, horizontal from the isometric perspective. It's the same distance function as you have, effectively. So if you keep the same world shape, then diagonal movement in one direction is disallowed. Ideally you'd also want to go back to to map shapes of civ 2 and 3.

Here's a iso grid with hex distances drawn on it.



Simple!
Notice that it actually looks like a big hexagon.
 

Attachments

  • isohexgrid.png
    isohexgrid.png
    24.5 KB · Views: 4,179
well WHATEVER mode you guys go for I would suggest two things
1, a map script that might work for this with hexes (pipedream)

2. a layer that shows places a unit may move into, something akin to the Settler or archer bombard graphical layer in BTS
 
I heartily endorse this product and/or service.

I would love to see this implemented in Civ4 somehow, along with proper tile capacity and ranged warfare elements, and zone of control if possible. Those are the best aspects of Civ5 and are the ones I'm really eager to see back-ported to Civ4.
 
Great idea.

But two things.

1) Can you move the shown plots on the map?
2) You will destroy very many functions!!! It's not just the distance calculation and the move rule. All area calculations are a peace of toast. Keep that in mind.
 
Top Bottom