Once, I tried a z-level branch (https://github.com/BevapDin/Cataclysm-DDA/tree/official-z-level), and stumbled across some of your problems. It’s not fully functional and a bit old. Several rebases might have caused some damage and it might not even compile anymore, this is how I tried to solve some of the problems (maybe it helps):
Most functions form "families" where changing one requires changing the rest. For example, if you add a 'z' parameter to 'move_cost', you need to update all functions using it (so that they can pass some 'z' argument) and all functions used by it (so that it doesn't just drop the z and do something stupid).
Then don’t add a z parameter. Add an overloaded function that accepts a tripoint instead of x,y:
// 2D original (unchanged):
int map::move_cost(int x, int y) {
return ter_at(x, y).move_cost + furn_at(x, y).move_cost;
}
// overload for tripoint which simply forwards to the 2D version:
int map::move_cost(tripoint p) {
return move_cost(p.x, p.y);
}
Do this will all (or nearly all) of the map functions and you have a 3D interface in the map class. Of course, it ignores the z-component, but that can be fixed later.
For functions that do not actually access submaps (they only all other map functions, forwarding the coordinates), I moved the content of the 2D-function into the 3D-function (actually only changed the function declaration) and replaced all references to x,y with a single reference to p:
// ter_at and furn_at also have overloaded functions that accept a tripoint, see step above
terrain &map::ter_at(tripoint p) { ... }
// this once was the 2D original:
int map::move_cost(tripoint p) {
return ter_at(p).move_cost + furn_at(p).move_cost;
}
// This was the 3D version:
int map::move_cost(int x, int y) {
return move_cost(tripoint(x, y, 0)); // or use map::abs_sub.z as default z value
}
The 2D-function now forwards to the 3D-function, nearly the same as before just the other direction. Of course, ter_at (and the other function that access submaps) will forward the 3D point back to the 2D-ter_at function (and ignore the z-value), but at this point you can use either version (3D or 2D) of a function and it will do the right thing (call the 3D version and it will forward the 3D-point).
The only functions that really need to be ported are those that access the submaps. All the other functions are straight forward replacing code ([tt]x,y[/tt] to [tt]p[/tt] and [tt]int x, int y[/tt] to [tt]tripoint p[/tt]) and adding an overloaded for 2D. At this stage the map is essentially still 2D, but the 3D interface it exposes compiles just fine and works (as long as you only request things on the default z-level).
so that it doesn't just drop the z and do something stupid
For precisely that reason, I used tripoints instead of an additional z parameter. You can't accidentally loose the z-value if you simply forward the tripoint:
[code]
int map::move_cost(tripoint p) {
// the z-value is automatically forwarded with x and y:
return ter_at(p).move_cost + furn_at(p).move_cost;
// calling a 2D function is now actually more work:
return ter_at(p.x, p.y).move_cost + furn_at(p.x, p.y).move_cost;
}
int map::move_cost(int x, int y, int z) {
// Oops, I forgot to forward z to furn_at and the compiler won't warn me
return ter_at(x, y, z).move_cost + furn_at(x, y).move_cost;
}
[/code]
The 2D-functions have 1 parameter more (x [b]and[/b] y) than the 3D-functions (only p). The compiler will chose the right one based on the parameter count, you can not accidentally call the wrong one. If you call ter_at with only one parameter, it must be a tripoint otherwise the compiler will complain. If you call it with two parameters those must be ints.
Nice side effect of overloaded functions is this: once you think a specific 2D version (say map::flammable_items_at) is not used anymore and only the 3D version is used, you can remove the implementation of the 2D-function and compile. The compiler will tell you exactly where (if at all) it’s still used.