/* Determine the cost of this node, for road tracks */ static int32 NPFRoadPathCost(AyStar *as, AyStarNode *current, OpenListNode *parent) { TileIndex tile = current->tile; int32 cost = 0; /* Determine base length */ switch (GetTileType(tile)) { case MP_TUNNELBRIDGE: cost = IsTunnel(tile) ? NPFTunnelCost(current) : NPFBridgeCost(current); break; case MP_ROAD: cost = NPF_TILE_LENGTH; /* Increase the cost for level crossings */ if (IsLevelCrossing(tile)) cost += _settings_game.pf.npf.npf_crossing_penalty; break; case MP_STATION: { cost = NPF_TILE_LENGTH; const RoadStop *rs = RoadStop::GetByTile(tile, GetRoadStopType(tile)); if (IsDriveThroughStopTile(tile)) { /* Increase the cost for drive-through road stops */ cost += _settings_game.pf.npf.npf_road_drive_through_penalty; DiagDirection dir = TrackdirToExitdir(current->direction); if (!RoadStop::IsDriveThroughRoadStopContinuation(tile, tile - TileOffsByDiagDir(dir))) { /* When we're the first road stop in a 'queue' of them we increase * cost based on the fill percentage of the whole queue. */ const RoadStop::Entry *entry = rs->GetEntry(dir); cost += entry->GetOccupied() * _settings_game.pf.npf.npf_road_dt_occupied_penalty / entry->GetLength(); } } else { /* Increase cost for filled road stops */ cost += _settings_game.pf.npf.npf_road_bay_occupied_penalty * (!rs->IsFreeBay(0) + !rs->IsFreeBay(1)) / 2; } break; } default: break; } /* Determine extra costs */ /* Check for slope */ cost += NPFSlopeCost(current); /* Check for turns. Road vehicles only really drive diagonal, turns are * represented by non-diagonal tracks */ if (!IsDiagonalTrackdir(current->direction)) { cost += _settings_game.pf.npf.npf_road_curve_penalty; } NPFMarkTile(tile); DEBUG(npf, 4, "Calculating G for: (%d, %d). Result: %d", TileX(current->tile), TileY(current->tile), cost); return cost; }
/* Determine the cost of this node, for railway tracks */ static int32 NPFRailPathCost(AyStar *as, AyStarNode *current, OpenListNode *parent) { TileIndex tile = current->tile; Trackdir trackdir = current->direction; int32 cost = 0; /* HACK: We create a OpenListNode manually, so we can call EndNodeCheck */ OpenListNode new_node; /* Determine base length */ switch (GetTileType(tile)) { case MP_TUNNELBRIDGE: cost = IsTunnel(tile) ? NPFTunnelCost(current) : NPFBridgeCost(current); break; case MP_RAILWAY: cost = _trackdir_length[trackdir]; // Should be different for diagonal tracks break; case MP_ROAD: // Railway crossing cost = NPF_TILE_LENGTH; break; case MP_STATION: /* We give a station tile a penalty. Logically we would only want to give * station tiles that are not our destination this penalty. This would * discourage trains to drive through busy stations. But, we can just * give any station tile a penalty, because every possible route will get * this penalty exactly once, on its end tile (if it's a station) and it * will therefore not make a difference. */ cost = NPF_TILE_LENGTH + _settings_game.pf.npf.npf_rail_station_penalty; if (IsRailWaypoint(tile)) { NPFFindStationOrTileData *fstd = (NPFFindStationOrTileData*)as->user_target; if (fstd->v->current_order.IsType(OT_GOTO_WAYPOINT) && GetStationIndex(tile) == fstd->v->current_order.GetDestination()) { /* This waypoint is our destination; maybe this isn't an unreserved * one, so check that and if so see that as the last signal being * red. This way waypoints near stations should work better. */ const Train *train = Train::From(fstd->v); CFollowTrackRail ft(train); TileIndex t = tile; Trackdir td = trackdir; while (ft.Follow(t, td)) { assert(t != ft.m_new_tile); t = ft.m_new_tile; if (KillFirstBit(ft.m_new_td_bits) != TRACKDIR_BIT_NONE) { /* We encountered a junction; it's going to be too complex to * handle this perfectly, so just bail out. There is no simple * free path, so try the other possibilities. */ td = INVALID_TRACKDIR; break; } td = RemoveFirstTrackdir(&ft.m_new_td_bits); /* If this is a safe waiting position we're done searching for it */ if (IsSafeWaitingPosition(train, t, td, true, _settings_game.pf.forbid_90_deg)) break; } if (td == INVALID_TRACKDIR || !IsSafeWaitingPosition(train, t, td, true, _settings_game.pf.forbid_90_deg) || !IsWaitingPositionFree(train, t, td, _settings_game.pf.forbid_90_deg)) { cost += _settings_game.pf.npf.npf_rail_lastred_penalty; } } } break; default: break; } /* Determine extra costs */ /* Check for signals */ if (IsTileType(tile, MP_RAILWAY)) { if (HasSignalOnTrackdir(tile, trackdir)) { SignalType sigtype = GetSignalType(tile, TrackdirToTrack(trackdir)); /* Ordinary track with signals */ if (GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_RED) { /* Signal facing us is red */ if (!NPFGetFlag(current, NPF_FLAG_SEEN_SIGNAL)) { /* Penalize the first signal we * encounter, if it is red */ /* Is this a presignal exit or combo? */ if (!IsPbsSignal(sigtype)) { if (sigtype == SIGTYPE_EXIT || sigtype == SIGTYPE_COMBO) { /* Penalise exit and combo signals differently (heavier) */ cost += _settings_game.pf.npf.npf_rail_firstred_exit_penalty; } else { cost += _settings_game.pf.npf.npf_rail_firstred_penalty; } } } /* Record the state of this signal */ NPFSetFlag(current, NPF_FLAG_LAST_SIGNAL_RED, true); } else { /* Record the state of this signal */ NPFSetFlag(current, NPF_FLAG_LAST_SIGNAL_RED, false); } if (NPFGetFlag(current, NPF_FLAG_SEEN_SIGNAL)) { if (NPFGetFlag(current, NPF_FLAG_2ND_SIGNAL)) { NPFSetFlag(current, NPF_FLAG_3RD_SIGNAL, true); } else { NPFSetFlag(current, NPF_FLAG_2ND_SIGNAL, true); } } else { NPFSetFlag(current, NPF_FLAG_SEEN_SIGNAL, true); } NPFSetFlag(current, NPF_FLAG_LAST_SIGNAL_BLOCK, !IsPbsSignal(sigtype)); } if (HasPbsSignalOnTrackdir(tile, ReverseTrackdir(trackdir)) && !NPFGetFlag(current, NPF_FLAG_3RD_SIGNAL)) { cost += _settings_game.pf.npf.npf_rail_pbs_signal_back_penalty; } } /* Penalise the tile if it is a target tile and the last signal was * red */ /* HACK: We create a new_node here so we can call EndNodeCheck. Ugly as hell * of course... */ new_node.path.node = *current; if (as->EndNodeCheck(as, &new_node) == AYSTAR_FOUND_END_NODE && NPFGetFlag(current, NPF_FLAG_LAST_SIGNAL_RED)) cost += _settings_game.pf.npf.npf_rail_lastred_penalty; /* Check for slope */ cost += NPFSlopeCost(current); /* Check for turns */ if (current->direction != NextTrackdir((Trackdir)parent->path.node.direction)) cost += _settings_game.pf.npf.npf_rail_curve_penalty; /* TODO, with realistic acceleration, also the amount of straight track between * curves should be taken into account, as this affects the speed limit. */ /* Check for reverse in depot */ if (IsRailDepotTile(tile) && as->EndNodeCheck(as, &new_node) != AYSTAR_FOUND_END_NODE) { /* Penalise any depot tile that is not the last tile in the path. This * _should_ penalise every occurence of reversing in a depot (and only * that) */ cost += _settings_game.pf.npf.npf_rail_depot_reverse_penalty; } /* Check for occupied track */ cost += NPFReservedTrackCost(current); NPFMarkTile(tile); DEBUG(npf, 4, "Calculating G for: (%d, %d). Result: %d", TileX(current->tile), TileY(current->tile), cost); return cost; }