Road Pathfinder Modifications
Posted: 16 Jan 2009 00:39
Hi all,
After a quick discussion with Yexo and a sharp blow to the head I have decided to abandon my custom pathfinder in favour of making modifications to the library road pathfinder, so that any modifications I might make can be shared with others. So here they are...
EDIT: Diff 18/01/09 updated with a small bug fix.
I must admit I've never done a diff before so I hope this is the right format!
I have added the following functionality...
1) Is very simple, as I have simply exposed the ignore_tiles parameter from the aystar library.
This allows the pathfinder to build simple road loops by blocking the trivial path to the goal and forcing it to go around.
2) was much more difficult as I had the change the way the pathfinder checks if a road can be built. The pathfinder allows demolition on any tile that isn't buildable but that can be demolished in test mode. Unfortunately this means that construction of those tiles in entirely speculative until the tile has actually been demolished in exec mode. This means that the pathfinder cant use AIRoad.BuildRoad() in test mode to check if the road can be built. To get around this I used three calls of AIRoad.CanBuildConnectedRoadPartsHere(). One for the current tile, and one each for the next a previous tiles, for which we have to search for neighbours (the nn_tile and pp_tile). The other two checks are necessary for some cases where a path meets an existing road on a slope.
It looks ugly but I promise you it works, and it doesn't seem to impact performance dramatically. If you can find a better way though, without using test mode I'd love to know.
This is turned off by default. You activate it setting the allow_demolition parameter...
3) uses a simple array to store a list of [callback, args] tuples and then executes each one in the cost function and accumulates the result. This allows additional costs to be added to alter the pathfinders behaviour at runtime, and does so in a loosely-coupled manner.
Hope you find these useful
In particular I hope Yexo finds 2) useful.
I must confess that I haven't tested this extremely thoroughly, so if you find bugs please let me know. Plus if anyone has any other suggestions please post them.
After a quick discussion with Yexo and a sharp blow to the head I have decided to abandon my custom pathfinder in favour of making modifications to the library road pathfinder, so that any modifications I might make can be shared with others. So here they are...
EDIT: Diff 18/01/09 updated with a small bug fix.
I must admit I've never done a diff before so I hope this is the right format!
I have added the following functionality...
- 1) Allow tiles to be ignored
- 2) Optionally enable demolition to allow removal of town houses
- 3) Allow additional cost to be applied to nodes via registerable cost callback functions
1) Is very simple, as I have simply exposed the ignore_tiles parameter from the aystar library.
Code: Select all
function InitializePath(sources, goals, ignored_tiles = []) {
...
this._pathfinder.InitializePath(nsources, goals, ignored_tiles);
}
2) was much more difficult as I had the change the way the pathfinder checks if a road can be built. The pathfinder allows demolition on any tile that isn't buildable but that can be demolished in test mode. Unfortunately this means that construction of those tiles in entirely speculative until the tile has actually been demolished in exec mode. This means that the pathfinder cant use AIRoad.BuildRoad() in test mode to check if the road can be built. To get around this I used three calls of AIRoad.CanBuildConnectedRoadPartsHere(). One for the current tile, and one each for the next a previous tiles, for which we have to search for neighbours (the nn_tile and pp_tile). The other two checks are necessary for some cases where a path meets an existing road on a slope.
Code: Select all
function Road::_IsConnectable(cur_node, par_node, next_tile)
{
local offsets = [AIMap.GetTileIndex(0, 1), AIMap.GetTileIndex(0, -1),
AIMap.GetTileIndex(1, 0), AIMap.GetTileIndex(-1, 0)];
local connectable = true;
if (par_node != null) {
connectable = AIRoad.CanBuildConnectedRoadPartsHere(cur_node, par_node.GetTile(), next_tile);
foreach(offset in offsets) {
local pp_tile = par_node.GetTile() + offset;
local nn_tile = next_tile + offset;
if (AIRoad.AreRoadTilesConnected(par_node.GetTile(), pp_tile)) {
connectable = connectable && AIRoad.CanBuildConnectedRoadPartsHere(par_node.GetTile(), pp_tile, cur_node);
}
if (AIRoad.AreRoadTilesConnected(next_tile, nn_tile)) {
connectable = connectable && AIRoad.CanBuildConnectedRoadPartsHere(next_tile, nn_tile, cur_node);
}
}
}
if (AIRoad.IsDriveThroughRoadStationTile(cur_node)) {
connectable = connectable && (AIRoad.GetRoadStationFrontTile(cur_node) == next_tile
|| AIRoad.GetDriveThroughBackTile(cur_node) == next_tile);
}
if (AIRoad.IsDriveThroughRoadStationTile(next_tile)) {
connectable = connectable && (AIRoad.GetRoadStationFrontTile(next_tile) == cur_node
|| AIRoad.GetDriveThroughBackTile(next_tile) == cur_node);
}
return connectable;
}
This is turned off by default. You activate it setting the allow_demolition parameter...
Code: Select all
pathfinder.cost.allow_demolition = true;
pathfinder.cost.cost_demolition = 1250;
Code: Select all
function RegisterCostCallback(callback, ...) {
local args = [];
for(local c = 0; c < vargc; c++) {
args.append(vargv[c]);
}
this._cost_callbacks.push([callback, args]);
}
Code: Select all
foreach(item in self._cost_callbacks) {
local args = [{}, new_tile, prev_tile];
args.extend(item[1]);
local value = item[0].acall(args);
if (typeof(value) != "integer") {
throw("Invalid return type from cost callback");
}
cost += value;
}

I must confess that I haven't tested this extremely thoroughly, so if you find bugs please let me know. Plus if anyone has any other suggestions please post them.