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.