diff --git a/src/dock_gui.cpp b/src/dock_gui.cpp index 7ba951d141..2cc37266a5 100644 --- a/src/dock_gui.cpp +++ b/src/dock_gui.cpp @@ -167,7 +167,9 @@ struct BuildDocksToolbarWindow : Window { break; case WID_DT_DEPOT: // Build depot button - if (HandlePlacePushButton(this, WID_DT_DEPOT, SPR_CURSOR_SHIP_DEPOT, HT_RECT)) ShowBuildDocksDepotPicker(this); + if (HandlePlacePushButton(this, widget, SPR_CURSOR_SHIP_DEPOT, HT_RECT)) { + ShowBuildDocksDepotPicker(this); + } break; case WID_DT_STATION: // Build station button @@ -207,9 +209,16 @@ struct BuildDocksToolbarWindow : Window { PlaceProc_DemolishArea(tile); break; - case WID_DT_DEPOT: // Build depot button - Command::Post(STR_ERROR_CAN_T_BUILD_SHIP_DEPOT, CcBuildDocks, tile, _ship_depot_direction); + case WID_DT_DEPOT: { // Build depot button + CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP); + + ViewportPlaceMethod vpm = _ship_depot_direction != AXIS_X ? VPM_LIMITED_X_FIXED_Y : VPM_LIMITED_Y_FIXED_X; + VpSetPlaceSizingLimit(_settings_game.depot.depot_spread); + VpStartPlaceSizing(tile, vpm, DDSP_BUILD_DEPOT); + /* Select tiles now to prevent selection from flickering. */ + VpSelectTilesWithMethod(pt.x, pt.y, vpm); break; + } case WID_DT_STATION: { // Build station button /* Determine the watery part of the dock. */ @@ -263,6 +272,15 @@ struct BuildDocksToolbarWindow : Window { case DDSP_CREATE_RIVER: Command::Post(STR_ERROR_CAN_T_PLACE_RIVERS, CcPlaySound_CONSTRUCTION_WATER, end_tile, start_tile, WATER_CLASS_RIVER, _ctrl_pressed); break; + case DDSP_BUILD_DEPOT: { + bool adjacent = _ctrl_pressed; + auto proc = [=](DepotID join_to) -> bool { + return Command::Post(STR_ERROR_CAN_T_BUILD_SHIP_DEPOT, CcBuildDocks, start_tile, _ship_depot_direction, adjacent, join_to, end_tile); + }; + + ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_SHIP); + break; + } default: break; } @@ -520,10 +538,13 @@ struct BuildDocksDepotWindow : public PickerWindowBase { private: static void UpdateDocksDirection() { + VpSetPlaceFixedSize(2); if (_ship_depot_direction != AXIS_X) { SetTileSelectSize(1, 2); + _thd.select_method = VPM_LIMITED_X_FIXED_Y; } else { SetTileSelectSize(2, 1); + _thd.select_method = VPM_LIMITED_Y_FIXED_X; } } @@ -538,6 +559,7 @@ public: void Close([[maybe_unused]] int data = 0) override { CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP); + VpResetFixedSize(); this->PickerWindowBase::Close(); } diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 91dff791db..89554c655c 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -952,22 +952,32 @@ CommandCost CmdRemoveRailroadTrack(DoCommandFlag flags, TileIndex end_tile, Tile /** * Build a train depot * @param flags operation to perform - * @param tile position of the train depot + * @param tile first position of the train depot * @param railtype rail type * @param dir entrance direction + * @param adjacent allow adjacent depots + * @param join_to depot to join to + * @param end_tile end tile of the area to be built * @return the cost of this operation or an error * * @todo When checking for the tile slope, * distinguish between "Flat land required" and "land sloped in wrong direction" */ -CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir) +CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir, bool adjacent, DepotID join_to, TileIndex end_tile) { /* check railtype and valid direction for depot (0 through 3), 4 in total */ if (!ValParamRailType(railtype) || !IsValidDiagDirection(dir)) return CMD_ERROR; - Slope tileh = GetTileSlope(tile); - CommandCost cost(EXPENSES_CONSTRUCTION); + TileArea ta(tile, end_tile); + Depot *depot = nullptr; + + /* Create a new depot or find a depot to join to. */ + CommandCost ret = FindJoiningDepot(ta, VEH_TRAIN, join_to, depot, adjacent, flags); + if (ret.Failed()) return ret; + + uint8_t num_new_depot_tiles = 0; + uint8_t num_rotated_depot_tiles = 0; /* Prohibit construction if * The tile is non-flat AND @@ -975,58 +985,59 @@ CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType rai * 2) the tile is steep i.e. spans two height levels * 3) the exit points in the wrong direction */ + for (Tile t : ta) { + if (IsBridgeAbove(t)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); - if (tileh != SLOPE_FLAT) { - if (!_settings_game.construction.build_on_slopes || !CanBuildDepotByTileh(dir, tileh)) { - return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + Slope tileh = GetTileSlope(t); + if (tileh != SLOPE_FLAT) { + if (!_settings_game.construction.build_on_slopes || + !CanBuildDepotByTileh(dir, tileh)) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } + cost.AddCost(_price[PR_BUILD_FOUNDATION]); + } + + /* Check whether a depot tile exists and it needs to be rotated. */ + if (IsRailDepotTile(t) && GetDepotIndex(t) == join_to && railtype == GetRailType(t)) { + if (dir == GetRailDepotDirection(t)) continue; + + ret = EnsureNoVehicleOnGround(t); + if (ret.Failed()) return ret; + + num_rotated_depot_tiles++; + if (flags & DC_EXEC) { + SetRailDepotExitDirection(t, dir); + } + } else { + cost.AddCost(Command::Do(flags, t)); + if (cost.Failed()) return cost; + + num_new_depot_tiles++; + if (flags & DC_EXEC) { + MakeRailDepot(t, _current_company, depot->index, dir, railtype); + MarkTileDirtyByTile(t); + } + } + + if (flags & DC_EXEC) { + AddSideToSignalBuffer(t, INVALID_DIAGDIR, _current_company); + YapfNotifyTrackLayoutChange(t, DiagDirToDiagTrack(dir)); + MarkTileDirtyByTile(t); } - cost.AddCost(_price[PR_BUILD_FOUNDATION]); } - /* Allow the user to rotate the depot instead of having to destroy it and build it again */ - bool rotate_existing_depot = false; - if (IsRailDepotTile(tile) && railtype == GetRailType(tile)) { - CommandCost ret = CheckTileOwnership(tile); - if (ret.Failed()) return ret; + if (num_new_depot_tiles + num_rotated_depot_tiles == 0) return CommandCost(); - if (dir == GetRailDepotDirection(tile)) return CommandCost(); - - ret = EnsureNoVehicleOnGround(tile); - if (ret.Failed()) return ret; - - rotate_existing_depot = true; - } - - if (!rotate_existing_depot) { - cost.AddCost(Command::Do(flags, tile)); - if (cost.Failed()) return cost; - - if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); - - if (!Depot::CanAllocateItem()) return CMD_ERROR; - } + cost.AddCost(_price[PR_BUILD_DEPOT_TRAIN] * (num_new_depot_tiles + num_rotated_depot_tiles)); + cost.AddCost(RailBuildCost(railtype) * (num_new_depot_tiles + num_rotated_depot_tiles)); if (flags & DC_EXEC) { - if (rotate_existing_depot) { - SetRailDepotExitDirection(tile, dir); - } else { - Depot *d = new Depot(tile, VEH_TRAIN, _current_company); - d->build_date = TimerGameCalendar::date; - - MakeRailDepot(tile, _current_company, d->index, dir, railtype); - MakeDefaultName(d); - - Company::Get(_current_company)->infrastructure.rail[railtype]++; - DirtyCompanyInfrastructureWindows(_current_company); - } - - MarkTileDirtyByTile(tile); - AddSideToSignalBuffer(tile, INVALID_DIAGDIR, _current_company); - YapfNotifyTrackLayoutChange(tile, DiagDirToDiagTrack(dir)); + Company::Get(_current_company)->infrastructure.rail[railtype] += num_new_depot_tiles; + DirtyCompanyInfrastructureWindows(_current_company); + depot->AfterAddRemove(ta, true); + if (join_to == NEW_DEPOT) MakeDefaultName(depot); } - cost.AddCost(_price[PR_BUILD_DEPOT_TRAIN]); - cost.AddCost(RailBuildCost(railtype)); return cost; } @@ -1558,6 +1569,7 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s if (area_start >= Map::Size()) return CMD_ERROR; TrainList affected_trains; + std::vector affected_depots; CommandCost cost(EXPENSES_CONSTRUCTION); CommandCost error = CommandCost(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); // by default, there is no track to convert. @@ -1653,11 +1665,11 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s /* notify YAPF about the track layout change */ YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile)); - /* Update build vehicle window related to this depot */ - DepotID depot_id = GetDepotIndex(tile); - InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); - InvalidateWindowData(WC_BUILD_VEHICLE, depot_id); + if (find(affected_depots.begin(), affected_depots.end(), (tile)) == affected_depots.end()) { + affected_depots.push_back(GetDepotIndex(tile)); + } } + found_convertible_track = true; cost.AddCost(RailConvertCost(type, totype)); break; @@ -1755,6 +1767,13 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s } if (flags & DC_EXEC) { + /* Update affected depots. */ + for (auto &depot_tile : affected_depots) { + Depot *dep = Depot::Get(depot_tile); + dep->RescanDepotTiles(); + InvalidateWindowData(WC_VEHICLE_DEPOT, dep->index); + } + /* Railtype changed, update trains as when entering different track */ for (Train *v : affected_trains) { v->ConsistChanged(CCF_TRACK); @@ -1775,9 +1794,11 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) if (ret.Failed()) return ret; if (flags & DC_EXEC) { - /* read variables before the depot is removed */ + Depot *depot = Depot::GetByTile(tile); + Company *c = Company::GetIfValid(depot->owner); + assert(c != nullptr); + DiagDirection dir = GetRailDepotDirection(tile); - Owner owner = GetTileOwner(tile); Train *v = nullptr; if (HasDepotReservation(tile)) { @@ -1785,14 +1806,17 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) if (v != nullptr) FreeTrainTrackReservation(v); } - Company::Get(owner)->infrastructure.rail[GetRailType(tile)]--; - DirtyCompanyInfrastructureWindows(owner); + c->infrastructure.rail[GetRailType(tile)]--; + DirtyCompanyInfrastructureWindows(c->index); - delete Depot::GetByTile(tile); DoClearSquare(tile); - AddSideToSignalBuffer(tile, dir, owner); + + AddSideToSignalBuffer(tile, dir, c->index); + YapfNotifyTrackLayoutChange(tile, DiagDirToDiagTrack(dir)); if (v != nullptr) TryPathReserve(v, true); + + depot->AfterAddRemove(TileArea(tile), false); } return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_TRAIN]); diff --git a/src/rail_cmd.h b/src/rail_cmd.h index b9f6c0cc03..ce10c4fd77 100644 --- a/src/rail_cmd.h +++ b/src/rail_cmd.h @@ -19,7 +19,7 @@ CommandCost CmdBuildRailroadTrack(DoCommandFlag flags, TileIndex end_tile, TileI CommandCost CmdRemoveRailroadTrack(DoCommandFlag flags, TileIndex end_tile, TileIndex start_tile, Track track); CommandCost CmdBuildSingleRail(DoCommandFlag flags, TileIndex tile, RailType railtype, Track track, bool auto_remove_signals); CommandCost CmdRemoveSingleRail(DoCommandFlag flags, TileIndex tile, Track track); -CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir); +CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir, bool adjacent, DepotID depot_id, TileIndex end_tile); CommandCost CmdBuildSingleSignal(DoCommandFlag flags, TileIndex tile, Track track, SignalType sigtype, SignalVariant sigvar, bool convert_signal, bool skip_existing_signals, bool ctrl_pressed, SignalType cycle_start, SignalType cycle_stop, uint8_t num_dir_cycle, uint8_t signals_copy); CommandCost CmdRemoveSingleSignal(DoCommandFlag flags, TileIndex tile, Track track); CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_start, RailType totype, bool diagonal); @@ -40,6 +40,6 @@ DEF_CMD_TRAIT(CMD_REMOVE_SIGNAL_TRACK, CmdRemoveSignalTrack, CMD_AUTO, CommandCallback CcPlaySound_CONSTRUCTION_RAIL; CommandCallback CcStation; CommandCallback CcBuildRailTunnel; -void CcRailDepot(Commands cmd, const CommandCost &result, TileIndex tile, RailType rt, DiagDirection dir); +void CcRailDepot(Commands cmd, const CommandCost &result, TileIndex tile, RailType rt, DiagDirection dir, bool adjacent, DepotID join_to, TileIndex end_tile); #endif /* RAIL_CMD_H */ diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index dcb0780919..271700e8c8 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -117,7 +117,7 @@ static void GenericPlaceRail(TileIndex tile, Track track) */ static void PlaceExtraDepotRail(TileIndex tile, DiagDirection dir, Track track) { - if (GetRailTileType(tile) == RAIL_TILE_DEPOT) return; + if (IsRailDepot(tile)) return; if (GetRailTileType(tile) == RAIL_TILE_SIGNALS && !_settings_client.gui.auto_remove_signals) return; if ((GetTrackBits(tile) & DiagdirReachesTracks(dir)) == 0) return; @@ -138,24 +138,26 @@ static const DiagDirection _place_depot_extra_dir[12] = { DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_NW, DIAGDIR_NE, }; -void CcRailDepot(Commands, const CommandCost &result, TileIndex tile, RailType, DiagDirection dir) +void CcRailDepot(Commands, const CommandCost &result, TileIndex start_tile, RailType, DiagDirection dir, bool, DepotID, TileIndex end_tile) { if (result.Failed()) return; - if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, tile); + if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, start_tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); - tile += TileOffsByDiagDir(dir); + TileArea ta(start_tile, end_tile); + for (TileIndex t : ta) { + TileIndex tile = t + TileOffsByDiagDir(dir); - if (IsTileType(tile, MP_RAILWAY)) { - PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir], _place_depot_extra_track[dir]); + if (IsTileType(tile, MP_RAILWAY)) { + PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir], _place_depot_extra_track[dir]); - /* Don't place the rail straight out of the depot of there is another depot across from it. */ - Tile double_depot_tile = tile + TileOffsByDiagDir(dir); - bool is_double_depot = IsValidTile(double_depot_tile) && IsRailDepotTile(double_depot_tile); - if (!is_double_depot) PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 4], _place_depot_extra_track[dir + 4]); + Tile double_depot_tile = tile + TileOffsByDiagDir(dir); + bool is_double_depot = IsValidTile(double_depot_tile) && IsRailDepotTile(double_depot_tile); + if (!is_double_depot) PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 4], _place_depot_extra_track[dir + 4]); - PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 8], _place_depot_extra_track[dir + 8]); + PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 8], _place_depot_extra_track[dir + 8]); + } } } @@ -690,9 +692,14 @@ struct BuildRailToolbarWindow : Window { PlaceProc_DemolishArea(tile); break; - case WID_RAT_BUILD_DEPOT: - Command::Post(STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT, CcRailDepot, tile, _cur_railtype, _build_depot_direction); + case WID_RAT_BUILD_DEPOT: { + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); + + ViewportPlaceMethod vpm = (DiagDirToAxis(_build_depot_direction) == 0) ? VPM_X_LIMITED : VPM_Y_LIMITED; + VpStartPlaceSizing(tile, vpm, DDSP_BUILD_DEPOT); + VpSetPlaceSizingLimit(_settings_game.depot.depot_spread); break; + } case WID_RAT_BUILD_WAYPOINT: PlaceRail_Waypoint(tile); @@ -788,6 +795,17 @@ struct BuildRailToolbarWindow : Window { } } break; + + case DDSP_BUILD_DEPOT: { + bool adjacent = _ctrl_pressed; + + auto proc = [=](DepotID join_to) -> bool { + return Command::Post(STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT, CcRailDepot, start_tile, _cur_railtype, _build_depot_direction, adjacent, join_to, end_tile); + }; + + ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_TRAIN); + break; + } } } } diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 7760a90fbe..e6a933ce59 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -1141,66 +1141,79 @@ std::tuple CmdRemoveLongRoad(DoCommandFlag flags, TileIndex * @param flags operation to perform * @param rt road type * @param dir entrance direction + * @param adjacent allow adjacent depots + * @param depot_id depot to join to + * @param end_tile end tile of the depot to be built * @return the cost of this operation or an error * * @todo When checking for the tile slope, * distinguish between "Flat land required" and "land sloped in wrong direction" */ -CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, DiagDirection dir) +CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, DiagDirection dir, bool adjacent, DepotID join_to, TileIndex end_tile) { if (!ValParamRoadType(rt) || !IsValidDiagDirection(dir)) return CMD_ERROR; + TileArea ta(tile, end_tile); + assert(ta.w == 1 || ta.h == 1); + + /* Create a new depot or find a depot to join to. */ + Depot *depot = nullptr; + CommandCost ret = FindJoiningDepot(ta, VEH_ROAD, join_to, depot, adjacent, flags); + if (ret.Failed()) return ret; + + uint8_t num_new_depot_tiles = 0; + uint8_t num_rotated_depot_tiles = 0; + CommandCost cost(EXPENSES_CONSTRUCTION); + for (Tile t : ta) { + if (IsBridgeAbove(t)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); - Slope tileh = GetTileSlope(tile); - if (tileh != SLOPE_FLAT) { - if (!_settings_game.construction.build_on_slopes || !CanBuildDepotByTileh(dir, tileh)) { - return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + Slope tileh = GetTileSlope(t); + if (tileh != SLOPE_FLAT) { + if (!_settings_game.construction.build_on_slopes || !CanBuildDepotByTileh(dir, tileh)) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } + cost.AddCost(_price[PR_BUILD_FOUNDATION]); + } + + /* Check whether a depot tile exists and it needs to be rotated. */ + if (IsRoadDepotTile(t) && + GetDepotIndex(t) == join_to && + (HasRoadTypeTram(t) ? rt == GetRoadTypeTram(t) : rt == GetRoadTypeRoad(t))) { + if (dir == GetRoadDepotDirection(t)) continue; + + ret = EnsureNoVehicleOnGround(t); + if (ret.Failed()) return ret; + + num_rotated_depot_tiles++; + if (flags & DC_EXEC) { + SetRoadDepotExitDirection(t, dir); + MarkTileDirtyByTile(t); + } + + } else { + cost.AddCost(Command::Do(flags, t)); + if (cost.Failed()) return cost; + + num_new_depot_tiles++; + if (flags & DC_EXEC) { + MakeRoadDepot(t, _current_company, depot->index, dir, rt); + MarkTileDirtyByTile(t); + } } - cost.AddCost(_price[PR_BUILD_FOUNDATION]); } - /* Allow the user to rotate the depot instead of having to destroy it and build it again */ - bool rotate_existing_depot = false; - if (IsRoadDepotTile(tile) && (HasRoadTypeTram(tile) ? rt == GetRoadTypeTram(tile) : rt == GetRoadTypeRoad(tile))) - { - CommandCost ret = CheckTileOwnership(tile); - if (ret.Failed()) return ret; - - if (dir == GetRoadDepotDirection(tile)) return CommandCost(); - - ret = EnsureNoVehicleOnGround(tile); - if (ret.Failed()) return ret; - - rotate_existing_depot = true; - } - - if (!rotate_existing_depot) { - cost.AddCost(Command::Do(flags, tile)); - if (cost.Failed()) return cost; - - if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); - - if (!Depot::CanAllocateItem()) return CMD_ERROR; - } + if (num_new_depot_tiles + num_rotated_depot_tiles == 0) return CommandCost(); if (flags & DC_EXEC) { - if (rotate_existing_depot) { - SetRoadDepotExitDirection(tile, dir); - } else { - Depot *dep = new Depot(tile, VEH_ROAD, _current_company); - dep->build_date = TimerGameCalendar::date; - MakeRoadDepot(tile, _current_company, dep->index, dir, rt); - MakeDefaultName(dep); + /* A road depot has two road bits. */ + UpdateCompanyRoadInfrastructure(rt, _current_company, num_new_depot_tiles * ROAD_DEPOT_TRACKBIT_FACTOR); - /* A road depot has two road bits. */ - UpdateCompanyRoadInfrastructure(rt, _current_company, ROAD_DEPOT_TRACKBIT_FACTOR); - } - - MarkTileDirtyByTile(tile); + depot->AfterAddRemove(ta, true); + if (join_to == NEW_DEPOT) MakeDefaultName(depot); } - cost.AddCost(_price[PR_BUILD_DEPOT_ROAD]); + cost.AddCost(_price[PR_BUILD_DEPOT_ROAD] * (num_new_depot_tiles + num_rotated_depot_tiles)); return cost; } @@ -1215,7 +1228,8 @@ static CommandCost RemoveRoadDepot(TileIndex tile, DoCommandFlag flags) if (ret.Failed()) return ret; if (flags & DC_EXEC) { - Company *c = Company::GetIfValid(GetTileOwner(tile)); + Depot *depot = Depot::GetByTile(tile); + Company *c = Company::GetIfValid(depot->owner); if (c != nullptr) { /* A road depot has two road bits. */ RoadType rt = GetRoadTypeRoad(tile); @@ -1224,8 +1238,8 @@ static CommandCost RemoveRoadDepot(TileIndex tile, DoCommandFlag flags) DirtyCompanyInfrastructureWindows(c->index); } - delete Depot::GetByTile(tile); DoClearSquare(tile); + depot->AfterAddRemove(TileArea(tile), false); } return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_ROAD]); diff --git a/src/road_cmd.h b/src/road_cmd.h index 71883ddada..2ae7e170e6 100644 --- a/src/road_cmd.h +++ b/src/road_cmd.h @@ -13,6 +13,7 @@ #include "direction_type.h" #include "road_type.h" #include "command_type.h" +#include "depot_type.h" enum RoadStopClassID : uint16_t; @@ -22,7 +23,7 @@ void UpdateNearestTownForRoadTiles(bool invalidate); CommandCost CmdBuildLongRoad(DoCommandFlag flags, TileIndex end_tile, TileIndex start_tile, RoadType rt, Axis axis, DisallowedRoadDirections drd, bool start_half, bool end_half, bool is_ai); std::tuple CmdRemoveLongRoad(DoCommandFlag flags, TileIndex end_tile, TileIndex start_tile, RoadType rt, Axis axis, bool start_half, bool end_half); CommandCost CmdBuildRoad(DoCommandFlag flags, TileIndex tile, RoadBits pieces, RoadType rt, DisallowedRoadDirections toggle_drd, TownID town_id); -CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, DiagDirection dir); +CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, DiagDirection dir, bool adjacent, DepotID join_to, TileIndex end_tile); CommandCost CmdConvertRoad(DoCommandFlag flags, TileIndex tile, TileIndex area_start, RoadType to_type); DEF_CMD_TRAIT(CMD_BUILD_LONG_ROAD, CmdBuildLongRoad, CMD_AUTO | CMD_NO_WATER | CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION) @@ -33,7 +34,7 @@ DEF_CMD_TRAIT(CMD_CONVERT_ROAD, CmdConvertRoad, 0, CommandCallback CcPlaySound_CONSTRUCTION_OTHER; CommandCallback CcBuildRoadTunnel; -void CcRoadDepot(Commands cmd, const CommandCost &result, TileIndex tile, RoadType rt, DiagDirection dir); +void CcRoadDepot(Commands cmd, const CommandCost &result, TileIndex start_tile, RoadType rt, DiagDirection dir, bool adjacent, DepotID join_to, TileIndex end_tile); void CcRoadStop(Commands cmd, const CommandCost &result, TileIndex tile, uint8_t width, uint8_t length, RoadStopType, bool is_drive_through, DiagDirection dir, RoadType, RoadStopClassID spec_class, uint16_t spec_index, StationID, bool); #endif /* ROAD_CMD_H */ diff --git a/src/road_gui.cpp b/src/road_gui.cpp index 14764444b8..53a71d0d2c 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -171,13 +171,17 @@ void ConnectRoadToStructure(TileIndex tile, DiagDirection direction) } } -void CcRoadDepot(Commands, const CommandCost &result, TileIndex tile, RoadType, DiagDirection dir) +void CcRoadDepot(Commands, const CommandCost &result, TileIndex start_tile, RoadType, DiagDirection dir, bool, DepotID, TileIndex end_tile) { if (result.Failed()) return; - if (_settings_client.sound.confirm) SndPlayTileFx(SND_1F_CONSTRUCTION_OTHER, tile); + if (_settings_client.sound.confirm) SndPlayTileFx(SND_1F_CONSTRUCTION_OTHER, start_tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); - ConnectRoadToStructure(tile, dir); + + TileArea ta(start_tile, end_tile); + for (TileIndex tile : ta) { + ConnectRoadToStructure(tile, dir); + } } /** @@ -638,8 +642,10 @@ struct BuildRoadToolbarWindow : Window { break; case WID_ROT_DEPOT: - Command::Post(this->rti->strings.err_depot, CcRoadDepot, - tile, _cur_roadtype, _road_depot_orientation); + CloseWindowById(WC_SELECT_DEPOT, VEH_ROAD); + + VpSetPlaceSizingLimit(_settings_game.depot.depot_spread); + VpStartPlaceSizing(tile, (DiagDirToAxis(_road_depot_orientation) == 0) ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_DEPOT); break; case WID_ROT_BUILD_WAYPOINT: @@ -810,6 +816,18 @@ struct BuildRoadToolbarWindow : Window { } break; + case DDSP_BUILD_DEPOT: { + bool adjacent = _ctrl_pressed; + StringID error_string = this->rti->strings.err_depot; + + auto proc = [=](DepotID join_to) -> bool { + return Command::Post(error_string, CcRoadDepot, start_tile, _cur_roadtype, _road_depot_orientation, adjacent, join_to, end_tile); + }; + + ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_ROAD); + break; + } + case DDSP_CONVERT_ROAD: Command::Post(rti->strings.err_convert_road, CcPlaySound_CONSTRUCTION_OTHER, end_tile, start_tile, _cur_roadtype); break; diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 2ff9258374..37cfc04633 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -37,6 +37,7 @@ #include "framerate_type.h" #include "roadveh_cmd.h" #include "road_cmd.h" +#include "depot_base.h" #include "table/strings.h" @@ -250,6 +251,41 @@ void RoadVehUpdateCache(RoadVehicle *v, bool same_length) v->vcache.cached_max_speed = (max_speed != 0) ? max_speed * 4 : RoadVehInfo(v->engine_type)->max_speed; } +/** + * Find an adequate tile for placing an engine. + * @param[in,out] tile A tile of the depot. + * @param e Engine to be built. + * @param flags Flags of the command. + * @return CommandCost() or an error message if the depot is not appropriate. + */ +CommandCost FindDepotTileForPlacingEngine(TileIndex &tile, const Engine *e, DoCommandFlag flags) +{ + assert(IsRoadDepotTile(tile)); + + Depot *dep = Depot:: GetByTile(tile); + + /* Check that the vehicle can drive on some tile of the depot */ + RoadType rt = e->u.road.roadtype; + const RoadTypeInfo *rti = GetRoadTypeInfo(rt); + if ((dep->r_types.road_types & rti->powered_roadtypes) == 0) return_cmd_error(STR_ERROR_DEPOT_WRONG_DEPOT_TYPE); + + /* Use same tile if possible when replacing. */ + if (flags & DC_AUTOREPLACE) { + /* Use same tile if possible when replacing. */ + if (HasTileAnyRoadType(tile, rti->powered_roadtypes)) return CommandCost(); + } + + tile = INVALID_TILE; + for (auto &depot_tile : dep->depot_tiles) { + if (!HasTileAnyRoadType(depot_tile, rti->powered_roadtypes)) continue; + tile = depot_tile; + break; + } + + assert(tile != INVALID_TILE); + return CommandCost(); +} + /** * Build a road vehicle. * @param flags type of operation. @@ -260,10 +296,12 @@ void RoadVehUpdateCache(RoadVehicle *v, bool same_length) */ CommandCost CmdBuildRoadVehicle(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **ret) { - /* Check that the vehicle can drive on the road in question */ + assert(IsRoadDepotTile(tile)); RoadType rt = e->u.road.roadtype; const RoadTypeInfo *rti = GetRoadTypeInfo(rt); - if (!HasTileAnyRoadType(tile, rti->powered_roadtypes)) return_cmd_error(STR_ERROR_DEPOT_WRONG_DEPOT_TYPE); + + CommandCost check = FindDepotTileForPlacingEngine(tile, e, flags); + if (check.Failed()) return check; if (flags & DC_EXEC) { const RoadVehicleInfo *rvi = &e->u.road; diff --git a/src/script/api/script_marine.cpp b/src/script/api/script_marine.cpp index 6c04e19046..5e6d60a6c6 100644 --- a/src/script/api/script_marine.cpp +++ b/src/script/api/script_marine.cpp @@ -83,7 +83,7 @@ EnforcePrecondition(false, ::IsValidTile(front)); EnforcePrecondition(false, (::TileX(front) == ::TileX(tile)) != (::TileY(front) == ::TileY(tile))); - return ScriptObject::Command::Do(tile, ::TileX(front) == ::TileX(tile) ? AXIS_Y : AXIS_X); + return ScriptObject::Command::Do(tile, ::TileX(front) == ::TileX(tile) ? AXIS_Y : AXIS_X, false, INVALID_DEPOT, tile); } /* static */ bool ScriptMarine::BuildDock(TileIndex tile, StationID station_id) diff --git a/src/script/api/script_rail.cpp b/src/script/api/script_rail.cpp index ac1b9fc5a9..5ac4a9b059 100644 --- a/src/script/api/script_rail.cpp +++ b/src/script/api/script_rail.cpp @@ -145,7 +145,7 @@ DiagDirection entrance_dir = (::TileX(tile) == ::TileX(front)) ? (::TileY(tile) < ::TileY(front) ? DIAGDIR_SE : DIAGDIR_NW) : (::TileX(tile) < ::TileX(front) ? DIAGDIR_SW : DIAGDIR_NE); - return ScriptObject::Command::Do(tile, (::RailType)ScriptObject::GetRailType(), entrance_dir); + return ScriptObject::Command::Do(tile, (::RailType)ScriptObject::GetRailType(), entrance_dir, false, INVALID_DEPOT, tile); } /* static */ bool ScriptRail::BuildRailStation(TileIndex tile, RailTrack direction, SQInteger num_platforms, SQInteger platform_length, StationID station_id) diff --git a/src/script/api/script_road.cpp b/src/script/api/script_road.cpp index 25b7fc2631..9b29e2d4b9 100644 --- a/src/script/api/script_road.cpp +++ b/src/script/api/script_road.cpp @@ -535,7 +535,7 @@ static bool NeighbourHasReachableRoad(::RoadType rt, TileIndex start_tile, DiagD DiagDirection entrance_dir = (::TileX(tile) == ::TileX(front)) ? (::TileY(tile) < ::TileY(front) ? DIAGDIR_SE : DIAGDIR_NW) : (::TileX(tile) < ::TileX(front) ? DIAGDIR_SW : DIAGDIR_NE); - return ScriptObject::Command::Do(tile, ScriptObject::GetRoadType(), entrance_dir); + return ScriptObject::Command::Do(tile, ScriptObject::GetRoadType(), entrance_dir, false, INVALID_DEPOT, tile); } /* static */ bool ScriptRoad::_BuildRoadStationInternal(TileIndex tile, TileIndex front, RoadVehicleType road_veh_type, bool drive_through, StationID station_id) diff --git a/src/train_gui.cpp b/src/train_gui.cpp index d62d3dce55..51ad7a9d00 100644 --- a/src/train_gui.cpp +++ b/src/train_gui.cpp @@ -15,6 +15,7 @@ #include "vehicle_func.h" #include "zoom_func.h" #include "train_cmd.h" +#include "depot_map.h" #include "table/strings.h" @@ -29,11 +30,12 @@ void CcBuildWagon(Commands, const CommandCost &result, VehicleID new_veh_id, uint, uint16_t, CargoArray, TileIndex tile, EngineID, bool, CargoID, ClientID) { if (result.Failed()) return; + DepotID depot_id = GetDepotIndex(tile); /* find a locomotive in the depot. */ const Vehicle *found = nullptr; for (const Train *t : Train::Iterate()) { - if (t->IsFrontEngine() && t->tile == tile && t->IsStoppedInDepot()) { + if (t->IsFrontEngine() && t->IsStoppedInDepot() && GetDepotIndex(t->tile) == depot_id) { if (found != nullptr) return; // must be exactly one. found = t; } diff --git a/src/viewport.cpp b/src/viewport.cpp index 5e60dae16a..6a9da5e44d 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -1013,7 +1013,7 @@ DepotID _viewport_highlight_depot = INVALID_DEPOT; ///< Currently selected depot static TileHighlightType GetTileHighlightType(TileIndex t) { if (_viewport_highlight_depot != INVALID_DEPOT) { - if (IsDepotTile(t) && GetDepotIndex(t) == _viewport_highlight_depot) return THT_WHITE; + if (IsDepotTile(t) && GetDepotIndex(t) == _viewport_highlight_depot) return THT_BLUE; } if (_viewport_highlight_station != nullptr) { diff --git a/src/viewport_type.h b/src/viewport_type.h index b026650db9..a65ec9d9f7 100644 --- a/src/viewport_type.h +++ b/src/viewport_type.h @@ -122,6 +122,7 @@ enum ViewportDragDropSelectionProcess { DDSP_PLANT_TREES, ///< Plant trees DDSP_BUILD_BRIDGE, ///< Bridge placement DDSP_BUILD_OBJECT, ///< Build an object + DDSP_BUILD_DEPOT, ///< Depot placement /* Rail specific actions */ DDSP_PLACE_RAIL, ///< Rail placement diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index 54f943451b..7c265e81cc 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -94,66 +94,75 @@ static void MarkCanalsAndRiversAroundDirty(TileIndex tile) /** * Build a ship depot. * @param flags type of operation - * @param tile tile where ship depot is built + * @param tile first tile where ship depot is built * @param axis depot orientation (Axis) + * @param adjacent allow adjacent depots + * @param join_to depot to join to + * @param end_tile end tile of area to be built * @return the cost of this operation or an error */ -CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis) +CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis, bool adjacent, DepotID join_to, TileIndex end_tile) { if (!IsValidAxis(axis)) return CMD_ERROR; TileIndex tile2 = tile + (axis == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); - if (!HasTileWaterGround(tile) || !HasTileWaterGround(tile2)) { - return_cmd_error(STR_ERROR_MUST_BE_BUILT_ON_WATER); - } + TileArea complete_area(tile, end_tile); + complete_area.Add(tile2); + assert(complete_area.w == 2 || complete_area.h == 2); - if (IsBridgeAbove(tile) || IsBridgeAbove(tile2)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + TileArea northern_tiles(complete_area.tile); + northern_tiles.Add(complete_area.tile + (axis == AXIS_X ? TileDiffXY(0, complete_area.h - 1) : TileDiffXY(complete_area.w - 1, 0))); - if (!IsTileFlat(tile) || !IsTileFlat(tile2)) { - /* Prevent depots on rapids */ - return_cmd_error(STR_ERROR_SITE_UNSUITABLE); - } - - if (!Depot::CanAllocateItem()) return CMD_ERROR; - - WaterClass wc1 = GetWaterClass(tile); - WaterClass wc2 = GetWaterClass(tile2); - CommandCost cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_DEPOT_SHIP]); - - bool add_cost = !IsWaterTile(tile); - CommandCost ret = Command::Do(flags | DC_AUTO, tile); + /* Create a new depot or find a depot to join to. */ + Depot *depot = nullptr; + CommandCost ret = FindJoiningDepot(complete_area, VEH_SHIP, join_to, depot, adjacent, flags); if (ret.Failed()) return ret; - if (add_cost) { - cost.AddCost(ret); - } - add_cost = !IsWaterTile(tile2); - ret = Command::Do(flags | DC_AUTO, tile2); - if (ret.Failed()) return ret; - if (add_cost) { - cost.AddCost(ret); + + /* Get the cost of building all the ship depots. */ + CommandCost cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_DEPOT_SHIP] * northern_tiles.w * northern_tiles.h); + + /* Update infrastructure counts after the tile clears earlier. + * Clearing object tiles may result in water tiles which are already accounted for in the water infrastructure total. + * See: MakeWaterKeepingClass() */ + uint new_water_infra = 0; + + for (TileIndex t : complete_area) { + /* Build water depots in water valid tiles... */ + if (!IsValidTile(t) || !HasTileWaterGround(t)) return_cmd_error(STR_ERROR_MUST_BE_BUILT_ON_WATER); + + /* ... with no bridges above... */ + if (IsBridgeAbove(t)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + + /* ... and preventing depots on rapids. */ + if (!IsTileFlat(t)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE); + + /* Keep original water class before clearing tile. */ + WaterClass wc = GetWaterClass(t); + + /* Clear the tile. */ + bool add_cost = !IsWaterTile(t); + CommandCost ret = Command::Do(flags | DC_AUTO, t); + if (ret.Failed()) return ret; + if (add_cost) cost.AddCost(ret); + + if (wc == WATER_CLASS_CANAL && !(HasTileWaterClass(t) && GetWaterClass(t) == WATER_CLASS_CANAL && IsTileOwner(t, _current_company))) new_water_infra++; + + if (flags & DC_EXEC) { + DepotPart dp = northern_tiles.Contains(t) ? DEPOT_PART_NORTH : DEPOT_PART_SOUTH; + MakeShipDepot(t, _current_company, depot->index, dp, axis, wc); + CheckForDockingTile(t); + MarkTileDirtyByTile(t); + } } if (flags & DC_EXEC) { - Depot *depot = new Depot(tile, VEH_SHIP, _current_company); - depot->build_date = TimerGameCalendar::date; - - uint new_water_infra = 2 * LOCK_DEPOT_TILE_FACTOR; - /* Update infrastructure counts after the tile clears earlier. - * Clearing object tiles may result in water tiles which are already accounted for in the water infrastructure total. - * See: MakeWaterKeepingClass() */ - if (wc1 == WATER_CLASS_CANAL && !(HasTileWaterClass(tile) && GetWaterClass(tile) == WATER_CLASS_CANAL && IsTileOwner(tile, _current_company))) new_water_infra++; - if (wc2 == WATER_CLASS_CANAL && !(HasTileWaterClass(tile2) && GetWaterClass(tile2) == WATER_CLASS_CANAL && IsTileOwner(tile2, _current_company))) new_water_infra++; - - Company::Get(_current_company)->infrastructure.water += new_water_infra; + Company::Get(_current_company)->infrastructure.water += new_water_infra + + complete_area.w * complete_area.h * LOCK_DEPOT_TILE_FACTOR; DirtyCompanyInfrastructureWindows(_current_company); - MakeShipDepot(tile, _current_company, depot->index, DEPOT_PART_NORTH, axis, wc1); - MakeShipDepot(tile2, _current_company, depot->index, DEPOT_PART_SOUTH, axis, wc2); - CheckForDockingTile(tile); - CheckForDockingTile(tile2); - MarkTileDirtyByTile(tile); - MarkTileDirtyByTile(tile2); MakeDefaultName(depot); + depot->AfterAddRemove(complete_area, true); + if (join_to == NEW_DEPOT) MakeDefaultName(depot); } return cost; @@ -275,9 +284,8 @@ static CommandCost RemoveShipDepot(TileIndex tile, DoCommandFlag flags) bool do_clear = (flags & DC_FORCE_CLEAR_TILE) != 0; if (flags & DC_EXEC) { - delete Depot::GetByTile(tile); - - Company *c = Company::GetIfValid(GetTileOwner(tile)); + Depot *depot = Depot::GetByTile(tile); + Company *c = Company::GetIfValid(depot->owner); if (c != nullptr) { c->infrastructure.water -= 2 * LOCK_DEPOT_TILE_FACTOR; if (do_clear && GetWaterClass(tile) == WATER_CLASS_CANAL) c->infrastructure.water--; @@ -286,6 +294,8 @@ static CommandCost RemoveShipDepot(TileIndex tile, DoCommandFlag flags) if (!do_clear) MakeWaterKeepingClass(tile, GetTileOwner(tile)); MakeWaterKeepingClass(tile2, GetTileOwner(tile2)); + + depot->AfterAddRemove(TileArea(tile, tile2), false); } return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_SHIP]); diff --git a/src/water_cmd.h b/src/water_cmd.h index 1c56790327..60e00d4a4c 100644 --- a/src/water_cmd.h +++ b/src/water_cmd.h @@ -13,7 +13,7 @@ #include "command_type.h" #include "water_map.h" -CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis); +CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis, bool adjacent, DepotID depot_id, TileIndex end_tile); CommandCost CmdBuildCanal(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, WaterClass wc, bool diagonal); CommandCost CmdBuildLock(DoCommandFlag flags, TileIndex tile);