Allow converting track by individual track pieces instead of whole tiles

Ctrl-click on convert button

See: #509
This commit is contained in:
Jonathan G Rennison
2023-05-06 16:20:23 +01:00
parent 09e03fb6b2
commit 31f476220d
8 changed files with 379 additions and 5 deletions

View File

@@ -2323,6 +2323,36 @@ static Vehicle *UpdateTrainPowerProc(Vehicle *v, void *data)
return nullptr;
}
struct UpdateTrainPowerProcData {
TrainList *train_list;
TrackBits track_bits;
};
/** Update power of train under which is the railtype being converted */
static Vehicle *UpdateTrainPowerProcAcrossTunnelBridge(Vehicle *v, void *data)
{
UpdateTrainPowerProcData *utpp_data = static_cast<UpdateTrainPowerProcData*>(data);
TrackBits vehicle_track = Train::From(v)->track;
if (!(vehicle_track & TRACK_BIT_WORMHOLE) && !(utpp_data->track_bits & vehicle_track)) return nullptr;
include(*(utpp_data->train_list), Train::From(v)->First());
return nullptr;
}
/** Update power of train under which is the railtype being converted */
static Vehicle *UpdateTrainPowerProcOnTrackBits(Vehicle *v, void *data)
{
UpdateTrainPowerProcData *utpp_data = static_cast<UpdateTrainPowerProcData*>(data);
if (!(utpp_data->track_bits & Train::From(v)->track)) return nullptr;
include(*(utpp_data->train_list), Train::From(v)->First());
return nullptr;
}
struct EnsureNoIncompatibleRailtypeTrainOnGroundData {
int z;
RailType type;
@@ -2659,6 +2689,320 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
return found_convertible_track ? cost : error;
}
/**
* Convert rail on a stretch of track.
* @param tile start tile of drag
* @param flags operation to perform
* @param p1 end tile of drag
* @param p2 various bitstuffed elements
* - p2 = (bit 0-5) - railroad type normal/maglev (0 = normal, 1 = mono, 2 = maglev)
* - p2 = (bit 6-8) - track-orientation, valid values: 0-5 (Track enum)
* @param text unused
* @return the cost of this operation or an error
*/
CommandCost CmdConvertRailTrack(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
{
const RailType totype = Extract<RailType, 0, 6>(p2);
const TileIndex end_tile = p1;
if (!ValParamRailtype(totype)) return CMD_ERROR;
if (end_tile >= MapSize()) return CMD_ERROR;
const Track start_track = Extract<Track, 6, 3>(p2);
Trackdir trackdir = TrackToTrackdir(start_track);
CommandCost ret = ValidateAutoDrag(&trackdir, tile, end_tile);
if (ret.Failed()) return ret;
TrainList affected_trains;
CommandCost cost(EXPENSES_CONSTRUCTION);
CommandCost error = CommandCost(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); // by default, there is no track to convert.
bool found_convertible_track = false; // whether we actually did convert some track (see bug #7633)
std::vector<TileIndex> exclude_tiles;
auto advance_tile = [&]() -> bool {
if (tile == end_tile) return false;
tile += ToTileIndexDiff(_trackdelta[trackdir]);
/* toggle railbit for the non-diagonal tracks */
if (!IsDiagonalTrackdir(trackdir)) ToggleBit(trackdir, 0);
return true;
};
do {
if (std::find(exclude_tiles.begin(), exclude_tiles.end(), tile) != exclude_tiles.end()) continue;
const Track track = TrackdirToTrack(trackdir);
const TileType tt = GetTileType(tile);
TrackBits all_track_bits = TRACK_BIT_NONE;
/* Check if our track piece matches any track on tile */
switch (tt) {
case MP_RAILWAY:
if (IsPlainRail(tile)) {
if (!HasTrack(tile, track)) continue;
all_track_bits = GetTrackBits(tile);
} else if (IsRailDepot(tile)) {
if (GetRailDepotTrack(tile) != track) continue;
all_track_bits = TrackToTrackBits(track);
} else {
continue;
}
break;
case MP_STATION:
if (!HasStationRail(tile) || GetRailStationTrack(tile) != track) continue;
all_track_bits = GetRailStationTrackBits(tile);
break;
case MP_ROAD:
if (!IsLevelCrossing(tile) || GetCrossingRailTrack(tile) != track) continue;
if (RailNoLevelCrossings(totype)) {
error.MakeError(STR_ERROR_CROSSING_DISALLOWED_RAIL);
continue;
}
all_track_bits = GetCrossingRailBits(tile);
break;
case MP_TUNNELBRIDGE:
if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL || !HasBit(GetTunnelBridgeTrackBits(tile), track)) continue;
all_track_bits = GetTunnelBridgeTrackBits(tile);
break;
default: continue;
}
/* Original railtype we are converting from */
const RailType type = GetRailTypeByTrack(tile, track);
/* Converting to the same type or converting 'hidden' elrail -> rail */
if (type == totype || (_settings_game.vehicle.disable_elrails && totype == RAILTYPE_RAIL && type == RAILTYPE_ELECTRIC)) continue;
/* Trying to convert other's rail */
CommandCost ret = CheckTileOwnership(tile);
if (ret.Failed()) {
error = ret;
continue;
}
/* Track bits on the tile to convert */
const TrackBits track_bits = (all_track_bits == TRACK_BIT_HORZ || all_track_bits == TRACK_BIT_VERT) ? TrackToTrackBits(track) : all_track_bits;
std::vector<Train *> vehicles_affected;
auto find_train_reservations = [&vehicles_affected, &totype, &flags](TileIndex tile, TrackBits reserved) -> CommandCost {
if (!(flags & DC_EXEC) && _settings_game.vehicle.train_braking_model != TBM_REALISTIC) {
/* Nothing to do */
return CommandCost();
}
Track track;
while ((track = RemoveFirstTrack(&reserved)) != INVALID_TRACK) {
Train *v = GetTrainForReservation(tile, track);
bool check_train = false;
if (v != nullptr && !HasPowerOnRail(v->railtype, totype)) {
check_train = true;
} else if (v != nullptr && _settings_game.vehicle.train_braking_model == TBM_REALISTIC) {
RailType original = GetRailTypeByTrack(tile, track);
if ((uint)(GetRailTypeInfo(original)->max_speed - 1) > (uint)(GetRailTypeInfo(totype)->max_speed - 1)) {
check_train = true;
}
}
if (check_train) {
CommandCost ret = CheckTrainReservationPreventsTrackModification(v);
if (ret.Failed()) return ret;
/* No power on new rail type, reroute. */
if (flags & DC_EXEC) {
FreeTrainTrackReservation(v);
vehicles_affected.push_back(v);
}
}
}
return CommandCost();
};
auto yapf_notify_track_change = [](TileIndex tile, TrackBits tracks) {
while (tracks != TRACK_BIT_NONE) {
YapfNotifyTrackLayoutChange(tile, RemoveFirstTrack(&tracks));
}
};
/* Vehicle on the tile when not converting Rail <-> ElRail
* Tunnels and bridges have special check later */
if (tt != MP_TUNNELBRIDGE) {
if (!IsCompatibleRail(type, totype)) {
CommandCost ret = IsPlainRailTile(tile) ? EnsureNoIncompatibleRailtypeTrainOnTrackBits(tile, track_bits, totype) : EnsureNoIncompatibleRailtypeTrainOnGround(tile, totype);
if (ret.Failed()) {
error = ret;
continue;
}
}
CommandCost ret = find_train_reservations(tile, GetReservedTrackbits(tile) & track_bits);
if (ret.Failed()) return ret;
if (flags & DC_EXEC) { // we can safely convert, too
/* Update the company infrastructure counters. */
if (!IsRailStationTile(tile) || !IsStationTileBlocked(tile)) {
Company *c = Company::Get(GetTileOwner(tile));
uint num_pieces = IsLevelCrossingTile(tile) ? LEVELCROSSING_TRACKBIT_FACTOR : 1;
if (IsPlainRailTile(tile)) {
num_pieces = CountBits(track_bits);
if (TracksOverlap(track_bits)) num_pieces *= num_pieces;
}
c->infrastructure.rail[type] -= num_pieces;
c->infrastructure.rail[totype] += num_pieces;
DirtyCompanyInfrastructureWindows(c->index);
}
if (track_bits != all_track_bits) {
/* only partially converting the tile */
if (track_bits & TRACK_BIT_RT_1) {
SetRailType(tile, totype);
} else {
SetSecondaryRailType(tile, totype);
}
} else {
SetRailType(tile, totype);
if (IsPlainRailTile(tile)) SetSecondaryRailType(tile, totype);
}
MarkTileDirtyByTile(tile);
/* update power of train on this tile */
UpdateTrainPowerProcData data;
data.train_list = &affected_trains;
data.track_bits = track_bits;
FindVehicleOnPos(tile, VEH_TRAIN, &data, &UpdateTrainPowerProcOnTrackBits);
}
}
switch (tt) {
case MP_RAILWAY:
switch (GetRailTileType(tile)) {
case RAIL_TILE_DEPOT:
if (flags & DC_EXEC) {
/* notify YAPF about the track layout change */
YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile));
/* Update build vehicle window related to this depot */
InvalidateWindowData(WC_VEHICLE_DEPOT, tile);
InvalidateWindowData(WC_BUILD_VEHICLE, tile);
}
found_convertible_track = true;
cost.AddCost(RailConvertCost(type, totype));
break;
default: // RAIL_TILE_NORMAL, RAIL_TILE_SIGNALS
if (flags & DC_EXEC) {
/* notify YAPF about the track layout change */
yapf_notify_track_change(tile, track_bits);
}
found_convertible_track = true;
cost.AddCost(RailConvertCost(type, totype) * CountBits(track_bits));
break;
}
break;
case MP_TUNNELBRIDGE: {
TileIndex endtile = GetOtherTunnelBridgeEnd(tile);
const bool across = (GetAcrossTunnelBridgeTrackBits(tile) & track_bits) != TRACK_BIT_NONE;
if (across) exclude_tiles.push_back(endtile);
/* When not converting rail <-> el. rail, any vehicle cannot be in tunnel/bridge */
if (!IsCompatibleRail(type, totype)) {
CommandCost ret;
if (across) {
ret = TunnelBridgeIsFree(tile, endtile, nullptr, TBIFM_PRIMARY_ONLY);
} else {
ret = EnsureNoIncompatibleRailtypeTrainOnTrackBits(tile, track_bits, totype);
}
if (ret.Failed()) {
error = ret;
continue;
}
}
found_convertible_track = true;
if (across) {
uint num_primary_pieces = GetTunnelBridgeLength(tile, endtile) + CountBits(GetPrimaryTunnelBridgeTrackBits(tile)) + CountBits(GetPrimaryTunnelBridgeTrackBits(endtile));
cost.AddCost(num_primary_pieces * RailConvertCost(type, totype));
} else {
cost.AddCost(RailConvertCost(type, totype));
}
CommandCost ret = find_train_reservations(tile, GetTunnelBridgeReservationTrackBits(tile) & track_bits);
if (ret.Failed()) return ret;
if (across) {
ret = find_train_reservations(endtile, GetTunnelBridgeReservationTrackBits(endtile) & GetPrimaryTunnelBridgeTrackBits(endtile));
if (ret.Failed()) return ret;
}
if (across && (uint)(GetRailTypeInfo(type)->max_speed - 1) > (uint)(GetRailTypeInfo(totype)->max_speed - 1)) {
ret = CheckTrainInTunnelBridgePreventsTrackModification(tile, endtile);
if (ret.Failed()) return ret;
}
if (flags & DC_EXEC) {
SubtractRailTunnelBridgeInfrastructure(tile, endtile);
if (across) {
SetRailType(tile, totype);
SetRailType(endtile, totype);
} else {
SetSecondaryRailType(tile, totype);
}
UpdateTrainPowerProcData data;
data.train_list = &affected_trains;
data.track_bits = track_bits;
if (across) {
FindVehicleOnPos(tile, VEH_TRAIN, &data, &UpdateTrainPowerProcAcrossTunnelBridge);
data.track_bits = GetPrimaryTunnelBridgeTrackBits(endtile);
FindVehicleOnPos(endtile, VEH_TRAIN, &data, &UpdateTrainPowerProcAcrossTunnelBridge);
} else {
FindVehicleOnPos(tile, VEH_TRAIN, &data, &UpdateTrainPowerProcOnTrackBits);
}
/* notify YAPF about the track layout change */
yapf_notify_track_change(tile, track_bits);
if (across) yapf_notify_track_change(endtile, GetPrimaryTunnelBridgeTrackBits(endtile));
if (IsBridge(tile)) {
MarkBridgeDirty(tile);
} else {
MarkTileDirtyByTile(tile);
MarkTileDirtyByTile(endtile);
}
AddRailTunnelBridgeInfrastructure(tile, endtile);
DirtyCompanyInfrastructureWindows(Company::Get(GetTileOwner(tile))->index);
}
break;
}
default: // MP_STATION, MP_ROAD
if (flags & DC_EXEC) {
YapfNotifyTrackLayoutChange(tile, track);
}
found_convertible_track = true;
cost.AddCost(RailConvertCost(type, totype));
break;
}
for (uint i = 0; i < vehicles_affected.size(); ++i) {
TryPathReserve(vehicles_affected[i], true);
}
} while (advance_tile());
if (flags & DC_EXEC) {
/* Railtype changed, update trains as when entering different track */
for (Train *v : affected_trains) {
v->ConsistChanged(CCF_TRACK);
}
}
return found_convertible_track ? cost : error;
}
static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags)
{
if (_current_company != OWNER_WATER) {