/**
* Build a Bridge
* @param end_tile end tile
* @param flags type of operation
* @param p1 packed start tile coords (~ dx)
* @param p2 various bitstuffed elements
* - p2 = (bit 0- 7) - bridge type (hi bh)
* - p2 = (bit 8-11) - rail type or road types.
* - p2 = (bit 15-16) - transport type.
* @param text unused
* @return the cost of this operation or an error
*/
CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
{
CompanyID company = _current_company;
RailType railtype = INVALID_RAILTYPE;
RoadTypes roadtypes = ROADTYPES_NONE;
/* unpack parameters */
BridgeType bridge_type = GB(p2, 0, 8);
if (!IsValidTile(p1)) return_cmd_error(STR_ERROR_BRIDGE_THROUGH_MAP_BORDER);
TransportType transport_type = Extract<TransportType, 15, 2>(p2);
/* type of bridge */
switch (transport_type) {
case TRANSPORT_ROAD:
roadtypes = Extract<RoadTypes, 8, 2>(p2);
if (!HasExactlyOneBit(roadtypes) || !HasRoadTypesAvail(company, roadtypes)) return CMD_ERROR;
break;
case TRANSPORT_RAIL:
railtype = Extract<RailType, 8, 4>(p2);
if (!ValParamRailtype(railtype)) return CMD_ERROR;
break;
case TRANSPORT_WATER:
break;
default:
/* Airports don't have bridges. */
return CMD_ERROR;
}
TileIndex tile_start = p1;
TileIndex tile_end = end_tile;
if (company == OWNER_DEITY) {
if (transport_type != TRANSPORT_ROAD) return CMD_ERROR;
const Town *town = CalcClosestTownFromTile(tile_start);
company = OWNER_TOWN;
/* If we are not within a town, we are not owned by the town */
if (town == NULL || DistanceSquare(tile_start, town->xy) > town->cache.squared_town_zone_radius[HZB_TOWN_EDGE]) {
company = OWNER_NONE;
}
}
if (tile_start == tile_end) {
return_cmd_error(STR_ERROR_CAN_T_START_AND_END_ON);
}
Axis direction;
if (TileX(tile_start) == TileX(tile_end)) {
direction = AXIS_Y;
} else if (TileY(tile_start) == TileY(tile_end)) {
direction = AXIS_X;
} else {
return_cmd_error(STR_ERROR_START_AND_END_MUST_BE_IN);
}
if (tile_end < tile_start) Swap(tile_start, tile_end);
uint bridge_len = GetTunnelBridgeLength(tile_start, tile_end);
if (transport_type != TRANSPORT_WATER) {
/* set and test bridge length, availability */
CommandCost ret = CheckBridgeAvailability(bridge_type, bridge_len, flags);
if (ret.Failed()) return ret;
} else {
if (bridge_len > _settings_game.construction.max_bridge_length) return_cmd_error(STR_ERROR_BRIDGE_TOO_LONG);
}
int z_start;
int z_end;
Slope tileh_start = GetTileSlope(tile_start, &z_start);
Slope tileh_end = GetTileSlope(tile_end, &z_end);
bool pbs_reservation = false;
CommandCost terraform_cost_north = CheckBridgeSlopeNorth(direction, &tileh_start, &z_start);
CommandCost terraform_cost_south = CheckBridgeSlopeSouth(direction, &tileh_end, &z_end);
/* Aqueducts can't be built of flat land. */
if (transport_type == TRANSPORT_WATER && (tileh_start == SLOPE_FLAT || tileh_end == SLOPE_FLAT)) return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION);
if (z_start != z_end) return_cmd_error(STR_ERROR_BRIDGEHEADS_NOT_SAME_HEIGHT);
CommandCost cost(EXPENSES_CONSTRUCTION);
Owner owner;
bool is_new_owner;
if (IsBridgeTile(tile_start) && IsBridgeTile(tile_end) &&
GetOtherBridgeEnd(tile_start) == tile_end &&
GetTunnelBridgeTransportType(tile_start) == transport_type) {
/* Replace a current bridge. */
/* If this is a railway bridge, make sure the railtypes match. */
if (transport_type == TRANSPORT_RAIL && GetRailType(tile_start) != railtype) {
return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
}
/* Do not replace town bridges with lower speed bridges, unless in scenario editor. */
if (!(flags & DC_QUERY_COST) && IsTileOwner(tile_start, OWNER_TOWN) &&
GetBridgeSpec(bridge_type)->speed < GetBridgeSpec(GetBridgeType(tile_start))->speed &&
_game_mode != GM_EDITOR) {
Town *t = ClosestTownFromTile(tile_start, UINT_MAX);
if (t == NULL) {
return CMD_ERROR;
} else {
SetDParam(0, t->index);
return_cmd_error(STR_ERROR_LOCAL_AUTHORITY_REFUSES_TO_ALLOW_THIS);
}
}
/* Do not replace the bridge with the same bridge type. */
if (!(flags & DC_QUERY_COST) && (bridge_type == GetBridgeType(tile_start)) && (transport_type != TRANSPORT_ROAD || (roadtypes & ~GetRoadTypes(tile_start)) == 0)) {
return_cmd_error(STR_ERROR_ALREADY_BUILT);
}
/* Do not allow replacing another company's bridges. */
if (!IsTileOwner(tile_start, company) && !IsTileOwner(tile_start, OWNER_TOWN) && !IsTileOwner(tile_start, OWNER_NONE)) {
return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER);
}
cost.AddCost((bridge_len + 1) * _price[PR_CLEAR_BRIDGE]); // The cost of clearing the current bridge.
owner = GetTileOwner(tile_start);
/* If bridge belonged to bankrupt company, it has a new owner now */
is_new_owner = (owner == OWNER_NONE);
if (is_new_owner) owner = company;
switch (transport_type) {
case TRANSPORT_RAIL:
/* Keep the reservation, the path stays valid. */
pbs_reservation = HasTunnelBridgeReservation(tile_start);
break;
case TRANSPORT_ROAD:
/* Do not remove road types when upgrading a bridge */
roadtypes |= GetRoadTypes(tile_start);
break;
default: break;
}
} else {
/* Build a new bridge. */
bool allow_on_slopes = (_settings_game.construction.build_on_slopes && transport_type != TRANSPORT_WATER);
/* Try and clear the start landscape */
CommandCost ret = DoCommand(tile_start, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
if (ret.Failed()) return ret;
cost = ret;
if (transport_type != TRANSPORT_WATER) {
if (terraform_cost_north.Failed() || (terraform_cost_north.GetCost() != 0 && !allow_on_slopes)) return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION);
cost.AddCost(terraform_cost_north);
} else {
Slope tileh_north_aqueduct = GetTileSlope(tile_start);
if (tileh_north_aqueduct != ComplementSlope(tileh_end)) {
if (!IsInclinedSlope(tileh_north_aqueduct) && !IsInclinedSlope(GetTileSlope(tile_end))) return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION);
if (IsSteepSlope(tileh_north_aqueduct)) {
tileh_north_aqueduct = SlopeWithOneCornerRaised(OppositeCorner(GetHighestSlopeCorner((tileh_north_aqueduct ^ (SLOPE_ELEVATED | SLOPE_STEEP)) ^ tileh_end)));
} else {
tileh_north_aqueduct = ComplementSlope(tileh_end) ^ tileh_north_aqueduct;
}
/* Mark the tile as already cleared for the terraform command.
* Do this for all tiles (like trees), not only objects. */
ClearedObjectArea *coa = FindClearedObject(tile_start);
if (coa == NULL) {
coa = _cleared_object_areas.Append();
coa->first_tile = tile_start;
coa->area = TileArea(tile_start, 1, 1);
}
/* Hide the tile from the terraforming command */
TileIndex old_first_tile = coa->first_tile;
coa->first_tile = INVALID_TILE;
ret = DoCommand(tile_start, tileh_north_aqueduct, 1, flags, CMD_TERRAFORM_LAND);
coa->first_tile = old_first_tile;
if (ret.Failed()) return_cmd_error(STR_ERROR_UNABLE_TO_SMOOTH_LAND_AQUEDUCT);
cost.AddCost(ret);
}
}
/* Try and clear the end landscape */
ret = DoCommand(tile_end, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
if (ret.Failed()) return ret;
cost.AddCost(ret);
/* false - end tile slope check */
if (transport_type != TRANSPORT_WATER) {
if (terraform_cost_south.Failed() || (terraform_cost_south.GetCost() != 0 && !allow_on_slopes)) return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION);
cost.AddCost(terraform_cost_south);
} else {
Slope tileh_south_aqueduct = GetTileSlope(tile_end);
if (tileh_south_aqueduct != ComplementSlope(tileh_start)) {
if (!IsInclinedSlope(tileh_south_aqueduct) && !IsInclinedSlope(GetTileSlope(tile_start))) return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION);
if (IsSteepSlope(tileh_south_aqueduct)) {
tileh_south_aqueduct = SlopeWithOneCornerRaised(OppositeCorner(GetHighestSlopeCorner((tileh_south_aqueduct ^ (SLOPE_ELEVATED | SLOPE_STEEP)) ^ tileh_start)));
} else {
tileh_south_aqueduct = ComplementSlope(tileh_start) ^ tileh_south_aqueduct;
}
/* Mark the tile as already cleared for the terraform command.
* Do this for all tiles (like trees), not only objects. */
ClearedObjectArea *coa = FindClearedObject(tile_end);
if (coa == NULL) {
coa = _cleared_object_areas.Append();
coa->first_tile = tile_end;
coa->area = TileArea(tile_end, 1, 1);
}
/* Hide the tile from the terraforming command */
TileIndex old_first_tile = coa->first_tile;
coa->first_tile = INVALID_TILE;
ret = DoCommand(tile_end, tileh_south_aqueduct, 1, flags, CMD_TERRAFORM_LAND);
coa->first_tile = old_first_tile;
if (ret.Failed()) return_cmd_error(STR_ERROR_UNABLE_TO_SMOOTH_LAND_AQUEDUCT);
cost.AddCost(ret);
}
}
const TileIndex heads[] = {tile_start, tile_end};
for (int i = 0; i < 2; i++) {
if (IsBridgeAbove(heads[i])) {
TileIndex north_head = GetNorthernBridgeEnd(heads[i]);
if (direction == GetBridgeAxis(heads[i])) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
if (z_start + 1 == GetBridgeHeight(north_head)) {
return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
}
}
}
TileIndexDiff delta = (direction == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1));
for (TileIndex tile = tile_start + delta; tile != tile_end; tile += delta) {
if (GetTileMaxZ(tile) > z_start) return_cmd_error(STR_ERROR_BRIDGE_TOO_LOW_FOR_TERRAIN);
if (z_start >= (GetTileZ(tile) + _settings_game.construction.max_bridge_height)) {
/*
* Disallow too high bridges.
* Properly rendering a map where very high bridges (might) exist is expensive.
* See http://www.tt-forums.net/viewtopic.php?f=33&t=40844&start=980#p1131762
* for a detailed discussion. z_start here is one heightlevel below the bridge level.
*/
return_cmd_error(STR_ERROR_BRIDGE_TOO_HIGH_FOR_TERRAIN);
}
if (IsBridgeAbove(tile)) {
/* Disallow crossing bridges for the time being */
return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
}
switch (GetTileType(tile)) {
case MP_WATER:
if (!IsWater(tile) && !IsCoast(tile)) goto not_valid_below;
break;
case MP_RAILWAY:
if (!IsPlainRail(tile)) goto not_valid_below;
break;
case MP_ROAD:
if (IsRoadDepot(tile)) goto not_valid_below;
break;
case MP_TUNNELBRIDGE:
if (IsTunnel(tile)) break;
if (direction == DiagDirToAxis(GetTunnelBridgeDirection(tile))) goto not_valid_below;
if (z_start < GetBridgeHeight(tile)) goto not_valid_below;
break;
case MP_OBJECT: {
const ObjectSpec *spec = ObjectSpec::GetByTile(tile);
if ((spec->flags & OBJECT_FLAG_ALLOW_UNDER_BRIDGE) == 0) goto not_valid_below;
if (GetTileMaxZ(tile) + spec->height > z_start) goto not_valid_below;
break;
}
case MP_CLEAR:
break;
default:
not_valid_below:;
/* try and clear the middle landscape */
ret = DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
if (ret.Failed()) return ret;
cost.AddCost(ret);
break;
}
if (flags & DC_EXEC) {
/* We do this here because when replacing a bridge with another
* type calling SetBridgeMiddle isn't needed. After all, the
* tile already has the has_bridge_above bits set. */
SetBridgeMiddle(tile, direction);
}
}
owner = company;
is_new_owner = true;
}
/* do the drill? */
if (flags & DC_EXEC) {
DiagDirection dir = AxisToDiagDir(direction);
Company *c = Company::GetIfValid(company);
switch (transport_type) {
case TRANSPORT_RAIL:
/* Add to company infrastructure count if required. */
if (is_new_owner && c != NULL) c->infrastructure.rail[railtype] += (bridge_len + 2) * TUNNELBRIDGE_TRACKBIT_FACTOR;
MakeRailBridgeRamp(tile_start, owner, bridge_type, dir, railtype);
MakeRailBridgeRamp(tile_end, owner, bridge_type, ReverseDiagDir(dir), railtype);
SetTunnelBridgeReservation(tile_start, pbs_reservation);
SetTunnelBridgeReservation(tile_end, pbs_reservation);
break;
case TRANSPORT_ROAD: {
RoadTypes prev_roadtypes = IsBridgeTile(tile_start) ? GetRoadTypes(tile_start) : ROADTYPES_NONE;
if (is_new_owner) {
/* Also give unowned present roadtypes to new owner */
if (HasBit(prev_roadtypes, ROADTYPE_ROAD) && GetRoadOwner(tile_start, ROADTYPE_ROAD) == OWNER_NONE) ClrBit(prev_roadtypes, ROADTYPE_ROAD);
if (HasBit(prev_roadtypes, ROADTYPE_TRAM) && GetRoadOwner(tile_start, ROADTYPE_TRAM) == OWNER_NONE) ClrBit(prev_roadtypes, ROADTYPE_TRAM);
}
if (c != NULL) {
/* Add all new road types to the company infrastructure counter. */
RoadType new_rt;
FOR_EACH_SET_ROADTYPE(new_rt, roadtypes ^ prev_roadtypes) {
/* A full diagonal road tile has two road bits. */
c->infrastructure.road[new_rt] += (bridge_len + 2) * 2 * TUNNELBRIDGE_TRACKBIT_FACTOR;
}
}
Owner owner_road = HasBit(prev_roadtypes, ROADTYPE_ROAD) ? GetRoadOwner(tile_start, ROADTYPE_ROAD) : company;
Owner owner_tram = HasBit(prev_roadtypes, ROADTYPE_TRAM) ? GetRoadOwner(tile_start, ROADTYPE_TRAM) : company;
MakeRoadBridgeRamp(tile_start, owner, owner_road, owner_tram, bridge_type, dir, roadtypes);
MakeRoadBridgeRamp(tile_end, owner, owner_road, owner_tram, bridge_type, ReverseDiagDir(dir), roadtypes);
break;
}
case TRANSPORT_WATER:
if (is_new_owner && c != NULL) c->infrastructure.water += (bridge_len + 2) * TUNNELBRIDGE_TRACKBIT_FACTOR;
MakeAqueductBridgeRamp(tile_start, owner, dir);
MakeAqueductBridgeRamp(tile_end, owner, ReverseDiagDir(dir));
break;
default:
NOT_REACHED();
}
/* Mark all tiles dirty */
MarkBridgeDirty(tile_start, tile_end, AxisToDiagDir(direction), z_start);
DirtyCompanyInfrastructureWindows(company);
}
if ((flags & DC_EXEC) && transport_type == TRANSPORT_RAIL) {
Track track = AxisToTrack(direction);
AddSideToSignalBuffer(tile_start, INVALID_DIAGDIR, company);
YapfNotifyTrackLayoutChange(tile_start, track);
}
/* for human player that builds the bridge he gets a selection to choose from bridges (DC_QUERY_COST)
* It's unnecessary to execute this command every time for every bridge. So it is done only
* and cost is computed in "bridge_gui.c". For AI, Towns this has to be of course calculated
*/
Company *c = Company::GetIfValid(company);
if (!(flags & DC_QUERY_COST) || (c != NULL && c->is_ai)) {
bridge_len += 2; // begin and end tiles/ramps
switch (transport_type) {
case TRANSPORT_ROAD: cost.AddCost(bridge_len * _price[PR_BUILD_ROAD] * 2 * CountBits(roadtypes)); break;
case TRANSPORT_RAIL: cost.AddCost(bridge_len * RailBuildCost(railtype)); break;
default: break;
}
if (c != NULL) bridge_len = CalcBridgeLenCostFactor(bridge_len);
if (transport_type != TRANSPORT_WATER) {
cost.AddCost((int64)bridge_len * _price[PR_BUILD_BRIDGE] * GetBridgeSpec(bridge_type)->price >> 8);
} else {
/* Aqueducts use a separate base cost. */
cost.AddCost((int64)bridge_len * _price[PR_BUILD_AQUEDUCT]);
}
}
return cost;
}