diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 44d76650a1..be615812a4 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -41,6 +41,7 @@ #include "framerate_type.h" #include "aircraft_cmd.h" #include "vehicle_cmd.h" +#include "depot_base.h" #include "table/strings.h" @@ -136,7 +137,7 @@ static StationID FindNearestHangar(const Aircraft *v) if (v->current_order.IsType(OT_GOTO_STATION) || (v->current_order.IsType(OT_GOTO_DEPOT) && (v->current_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) == 0)) { last_dest = Station::GetIfValid(v->last_station_visited); - next_dest = Station::GetIfValid(v->current_order.GetDestination()); + next_dest = Station::GetIfValid(GetTargetDestination(v->current_order, true)); } else { last_dest = GetTargetAirportIfValid(v); next_dest = Station::GetIfValid(v->GetNextStoppingStation().value); @@ -407,9 +408,10 @@ ClosestDepot Aircraft::FindClosestDepot() if (station == INVALID_STATION) return ClosestDepot(); st = Station::Get(station); + assert(st->airport.hangar != nullptr); } - return ClosestDepot(st->xy, st->index); + return ClosestDepot(st->xy, st->airport.hangar->index); } static void CheckIfAircraftNeedsService(Aircraft *v) @@ -424,13 +426,13 @@ static void CheckIfAircraftNeedsService(Aircraft *v) * we don't want to consider going to a depot too. */ if (!v->current_order.IsType(OT_GOTO_DEPOT) && !v->current_order.IsType(OT_GOTO_STATION)) return; - const Station *st = Station::Get(v->current_order.GetDestination()); + const Station *st = Station::Get(GetTargetDestination(v->current_order, true)); assert(st != nullptr); /* only goto depot if the target airport has a depot */ if (st->airport.HasHangar() && CanVehicleUseStation(v, st)) { - v->current_order.MakeGoToDepot(st->index, ODTFB_SERVICE); + v->current_order.MakeGoToDepot(st->airport.hangar->index, ODTFB_SERVICE); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); } else if (v->current_order.IsType(OT_GOTO_DEPOT)) { v->current_order.MakeDummy(); @@ -892,7 +894,7 @@ static bool AircraftController(Aircraft *v) /* Jump into our "holding pattern" state machine if possible */ if (v->pos >= afc->nofelements) { v->pos = v->previous_pos = AircraftGetEntryPoint(v, afc, DIR_N); - } else if (v->targetairport != v->current_order.GetDestination()) { + } else if (v->targetairport != GetTargetDestination(v->current_order, true)) { /* If not possible, just get out of here fast */ v->state = FLYING; UpdateAircraftCache(v); @@ -1449,7 +1451,7 @@ static void AircraftLandAirplane(Aircraft *v) void AircraftNextAirportPos_and_Order(Aircraft *v) { if (v->current_order.IsType(OT_GOTO_STATION) || v->current_order.IsType(OT_GOTO_DEPOT)) { - v->targetairport = v->current_order.GetDestination(); + v->targetairport = GetTargetDestination(v->current_order, true); } const Station *st = GetTargetAirportIfValid(v); @@ -1488,7 +1490,9 @@ void AircraftLeaveHangar(Aircraft *v, Direction exit_dir) VehicleServiceInDepot(v); v->LeaveUnbunchingDepot(); SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + + /* When called from UpdateOldAircraft(), tile may not be a hangar. */ + if (IsHangarTile(v->tile)) InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); SetWindowClassesDirty(WC_AIRCRAFT_LIST); } @@ -1539,7 +1543,7 @@ static void AircraftEventHandler_InHangar(Aircraft *v, const AirportFTAClass *ap return; /* We are leaving a hangar, but have to go to the exact same one; re-enter */ - if (v->current_order.IsType(OT_GOTO_DEPOT) && v->current_order.GetDestination() == v->targetairport) { + if (v->current_order.IsType(OT_GOTO_DEPOT) && GetTargetDestination(v->current_order, true) == v->targetairport) { VehicleEnterDepot(v); return; } @@ -1548,7 +1552,7 @@ static void AircraftEventHandler_InHangar(Aircraft *v, const AirportFTAClass *ap if (AirportHasBlock(v, &apc->layout[v->pos], apc)) return; /* We are already at the target airport, we need to find a terminal */ - if (v->current_order.GetDestination() == v->targetairport) { + if (GetTargetDestination(v->current_order, true) == v->targetairport) { /* FindFreeTerminal: * 1. Find a free terminal, 2. Occupy it, 3. Set the vehicle's state to that terminal */ if (v->subtype == AIR_HELICOPTER) { @@ -1599,7 +1603,7 @@ static void AircraftEventHandler_AtTerminal(Aircraft *v, const AirportFTAClass * case OT_GOTO_STATION: // ready to fly to another airport break; case OT_GOTO_DEPOT: // visit hangar for servicing, sale, etc. - go_to_hangar = v->current_order.GetDestination() == v->targetairport; + go_to_hangar = GetTargetDestination(v->current_order, true) == v->targetairport; break; case OT_CONDITIONAL: /* In case of a conditional order we just have to wait a tick @@ -2103,7 +2107,7 @@ static bool AircraftEventHandler(Aircraft *v, int loop) /* Check the distance to the next destination. This code works because the target * airport is only updated after take off and not on the ground. */ Station *cur_st = Station::GetIfValid(v->targetairport); - Station *next_st = v->current_order.IsType(OT_GOTO_STATION) || v->current_order.IsType(OT_GOTO_DEPOT) ? Station::GetIfValid(v->current_order.GetDestination()) : nullptr; + Station *next_st = Station::GetIfValid(GetTargetDestination(v->current_order, true)); if (cur_st != nullptr && cur_st->airport.tile != INVALID_TILE && next_st != nullptr && next_st->airport.tile != INVALID_TILE) { uint dist = DistanceSquare(cur_st->airport.tile, next_st->airport.tile); @@ -2169,18 +2173,7 @@ void UpdateAirplanesOnNewStation(const Station *st) if (!v->IsNormalAircraft() || v->targetairport != st->index) continue; assert(v->state == FLYING); - Order *o = &v->current_order; - /* The aircraft is heading to a hangar, but the new station doesn't have one, - * or the aircraft can't land on the new station. Cancel current order. */ - if (o->IsType(OT_GOTO_DEPOT) && !(o->GetDepotOrderType() & ODTFB_PART_OF_ORDERS) && o->GetDestination() == st->index && - (!st->airport.HasHangar() || !CanVehicleUseStation(v, st))) { - o->MakeDummy(); - SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); - } v->pos = v->previous_pos = AircraftGetEntryPoint(v, ap, rotation); UpdateAircraftCache(v); } - - /* Heliports don't have a hangar. Invalidate all go to hangar orders from all aircraft. */ - if (!st->airport.HasHangar()) RemoveOrderFromAllVehicles(OT_GOTO_DEPOT, st->index, true); } diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 757f18d371..52f4588fbc 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -71,7 +71,10 @@ bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company) switch (type) { case VEH_TRAIN: { /* make sure the railtypes are compatible */ - if ((GetRailTypeInfo(e_from->u.rail.railtype)->compatible_railtypes & GetRailTypeInfo(e_to->u.rail.railtype)->compatible_railtypes) == 0) return false; + if (!_settings_game.depot.allow_no_comp_railtype_replacements && + (GetRailTypeInfo(e_from->u.rail.railtype)->compatible_railtypes & GetRailTypeInfo(e_to->u.rail.railtype)->compatible_railtypes) == 0) { + return false; + } /* make sure we do not replace wagons with engines or vice versa */ if ((e_from->u.rail.railveh_type == RAILVEH_WAGON) != (e_to->u.rail.railveh_type == RAILVEH_WAGON)) return false; @@ -79,11 +82,15 @@ bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company) } case VEH_ROAD: - /* make sure the roadtypes are compatible */ - if ((GetRoadTypeInfo(e_from->u.road.roadtype)->powered_roadtypes & GetRoadTypeInfo(e_to->u.road.roadtype)->powered_roadtypes) == ROADTYPES_NONE) return false; + if (!_settings_game.depot.allow_no_comp_roadtype_replacements) { + /* make sure the roadtypes are compatible */ + if ((GetRoadTypeInfo(e_from->u.road.roadtype)->powered_roadtypes & GetRoadTypeInfo(e_to->u.road.roadtype)->powered_roadtypes) == ROADTYPES_NONE) { + return false; + } - /* make sure that we do not replace a tram with a normal road vehicles or vice versa */ - if (HasBit(e_from->info.misc_flags, EF_ROAD_TRAM) != HasBit(e_to->info.misc_flags, EF_ROAD_TRAM)) return false; + /* make sure that we do not replace a tram with a normal road vehicles or vice versa */ + if (HasBit(e_from->info.misc_flags, EF_ROAD_TRAM) != HasBit(e_to->info.misc_flags, EF_ROAD_TRAM)) return false; + } break; case VEH_AIRCRAFT: @@ -519,6 +526,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon { Vehicle *old_head = *chain; assert(old_head->IsPrimaryVehicle()); + TileIndex tile = old_head->tile; CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, (Money)0); @@ -661,6 +669,9 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon if ((flags & DC_EXEC) != 0) CheckCargoCapacity(new_head); } + assert(IsValidTile(tile)); + if (!HasCompatibleDepotTile(tile, Train::From(new_head))) cost.MakeError(STR_ERROR_UNABLE_TO_FIND_APPROPRIATE_DEPOT_TILE); + /* If we are not in DC_EXEC undo everything, i.e. rearrange old vehicles. * We do this from back to front, so that the head of the temporary vehicle chain does not change all the time. * Note: The vehicle attach callback is disabled here :) */ diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 1a985eb64a..3d8ed88f52 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -38,6 +38,7 @@ #include "querystring_gui.h" #include "stringfilter_type.h" #include "hotkeys.h" +#include "depot_base.h" #include "widgets/build_vehicle_widget.h" @@ -1163,12 +1164,31 @@ enum BuildVehicleHotkeys { BVHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string }; +/** + * Return a unique window number for the BuildVehicleWindow. + * The BuildVehicleWindow can be opened for a valid depot or + * for a specific vehicle type ("Available Trains", "Available Ships"...). + * The corresponding unique window number is chosen as: + * - For existing depots, the depot id. + * - For vehicle types, it is MAX_DEPOTS + vehicle_type + * @param depot_id the depot id + * @param type the vehicle type + * @return the depot id for valid depots or MAX_DEPOTS + vehicle_type otherwise. + */ +DepotID GetBuildVehicleWindowNumber(DepotID depot_id, VehicleType type) +{ + assert(depot_id == INVALID_DEPOT || Depot::IsValidID(depot_id)); + assert(IsCompanyBuildableVehicleType(type)); + if (depot_id != INVALID_DEPOT) return depot_id; + return MAX_DEPOTS + type; +} + /** GUI for building vehicles. */ struct BuildVehicleWindow : Window { VehicleType vehicle_type; ///< Type of vehicles shown in the window. union { - RailType railtype; ///< Rail type to show, or #INVALID_RAILTYPE. - RoadType roadtype; ///< Road type to show, or #INVALID_ROADTYPE. + RailTypes railtypes; ///< Rail types to show, or #INVALID_RAILTYPES. + RoadTypes roadtypes; ///< Road types to show, or #INVALID_ROADTYPES. } filter; ///< Filter to apply. bool descending_sort_order; ///< Sort direction, @see _engine_sort_direction uint8_t sort_criteria; ///< Current sort criterium. @@ -1201,11 +1221,11 @@ struct BuildVehicleWindow : Window { } } - BuildVehicleWindow(WindowDesc &desc, TileIndex tile, VehicleType type) : Window(desc), vehicle_editbox(MAX_LENGTH_VEHICLE_NAME_CHARS * MAX_CHAR_LENGTH, MAX_LENGTH_VEHICLE_NAME_CHARS) + BuildVehicleWindow(WindowDesc &desc, DepotID depot_id, VehicleType type) : Window(desc), vehicle_editbox(MAX_LENGTH_VEHICLE_NAME_CHARS * MAX_CHAR_LENGTH, MAX_LENGTH_VEHICLE_NAME_CHARS) { this->vehicle_type = type; - this->listview_mode = tile == INVALID_TILE; - this->window_number = this->listview_mode ? (int)type : tile.base(); + this->listview_mode = depot_id == INVALID_DEPOT; + this->window_number = GetBuildVehicleWindowNumber(depot_id, type); this->sel_engine = INVALID_ENGINE; @@ -1240,16 +1260,13 @@ struct BuildVehicleWindow : Window { this->details_height = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9); - if (tile == INVALID_TILE) { - this->FinishInitNested(type); - } else { - this->FinishInitNested(tile); - } + this->FinishInitNested(this->window_number); this->querystrings[WID_BV_FILTER] = &this->vehicle_editbox; this->vehicle_editbox.cancel_button = QueryString::ACTION_CLEAR; - this->owner = (tile != INVALID_TILE) ? GetTileOwner(tile) : _local_company; + Depot *depot = Depot::GetIfValid(depot_id); + this->owner = depot != nullptr ? depot->owner : _local_company; this->eng_list.ForceRebuild(); this->GenerateBuildList(); // generate the list, since we need it in the next line @@ -1264,25 +1281,16 @@ struct BuildVehicleWindow : Window { /** Set the filter type according to the depot type */ void UpdateFilterByTile() { + Depot *depot = this->listview_mode ? nullptr : Depot::Get(this->window_number); + switch (this->vehicle_type) { default: NOT_REACHED(); case VEH_TRAIN: - if (this->listview_mode) { - this->filter.railtype = INVALID_RAILTYPE; - } else { - this->filter.railtype = GetRailType(this->window_number); - } + this->filter.railtypes = this->listview_mode ? INVALID_RAILTYPES : depot->r_types.rail_types; break; case VEH_ROAD: - if (this->listview_mode) { - this->filter.roadtype = INVALID_ROADTYPE; - } else { - this->filter.roadtype = GetRoadTypeRoad(this->window_number); - if (this->filter.roadtype == INVALID_ROADTYPE) { - this->filter.roadtype = GetRoadTypeTram(this->window_number); - } - } + this->filter.roadtypes = this->listview_mode ? INVALID_ROADTYPES : depot->r_types.road_types; break; case VEH_SHIP: @@ -1326,7 +1334,7 @@ struct BuildVehicleWindow : Window { if (!this->listview_mode) { /* Query for cost and refitted capacity */ - auto [ret, veh_id, refit_capacity, refit_mail, cargo_capacities] = Command::Do(DC_QUERY_COST, this->window_number, this->sel_engine, true, cargo, INVALID_CLIENT_ID); + auto [ret, veh_id, refit_capacity, refit_mail, cargo_capacities] = Command::Do(DC_QUERY_COST, Depot::Get(this->window_number)->xy, this->sel_engine, true, cargo, INVALID_CLIENT_ID); if (ret.Succeeded()) { this->te.cost = ret.GetCost() - e->GetCost(); this->te.capacity = refit_capacity; @@ -1401,7 +1409,7 @@ struct BuildVehicleWindow : Window { EngineID eid = e->index; const RailVehicleInfo *rvi = &e->u.rail; - if (this->filter.railtype != INVALID_RAILTYPE && !HasPowerOnRail(rvi->railtype, this->filter.railtype)) continue; + if (!this->listview_mode && !HasPowerOnRails(rvi->railtype, this->filter.railtypes)) continue; if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue; /* Filter now! So num_engines and num_wagons is valid */ @@ -1461,7 +1469,7 @@ struct BuildVehicleWindow : Window { if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue; EngineID eid = e->index; if (!IsEngineBuildable(eid, VEH_ROAD, _local_company)) continue; - if (this->filter.roadtype != INVALID_ROADTYPE && !HasPowerOnRoad(e->u.road.roadtype, this->filter.roadtype)) continue; + if (!this->listview_mode && !HasPowerOnRoads(e->u.road.roadtype, this->filter.roadtypes)) continue; /* Filter by name or NewGRF extra text */ if (!FilterByText(e)) continue; @@ -1501,7 +1509,7 @@ struct BuildVehicleWindow : Window { this->eng_list.clear(); - const Station *st = this->listview_mode ? nullptr : Station::GetByTile(this->window_number); + const Station *st = this->listview_mode ? nullptr : Depot::Get(this->window_number)->station; /* Make list of all available planes. * Also check to see if the previously selected plane is still available, @@ -1613,10 +1621,15 @@ struct BuildVehicleWindow : Window { CargoID cargo = this->cargo_filter_criteria; if (cargo == CargoFilterCriteria::CF_ANY || cargo == CargoFilterCriteria::CF_ENGINES || cargo == CargoFilterCriteria::CF_NONE) cargo = INVALID_CARGO; + + assert(Depot::IsValidID(this->window_number)); + Depot *depot = Depot::Get(this->window_number); + assert(depot->xy != INVALID_TILE); + if (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) { - Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildWagon, this->window_number, sel_eng, true, cargo, INVALID_CLIENT_ID); + Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildWagon, depot->xy, sel_eng, true, cargo, INVALID_CLIENT_ID); } else { - Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildPrimaryVehicle, this->window_number, sel_eng, true, cargo, INVALID_CLIENT_ID); + Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildPrimaryVehicle, depot->xy, sel_eng, true, cargo, INVALID_CLIENT_ID); } /* Update last used variant in hierarchy and refresh if necessary. */ @@ -1736,11 +1749,21 @@ struct BuildVehicleWindow : Window { switch (widget) { case WID_BV_CAPTION: if (this->vehicle_type == VEH_TRAIN && !this->listview_mode) { - const RailTypeInfo *rti = GetRailTypeInfo(this->filter.railtype); - SetDParam(0, rti->strings.build_caption); + uint num_railtypes = CountBits(this->filter.railtypes); + if (num_railtypes != 1) { + SetDParam(0, STR_BUY_VEHICLE_TRAIN_ALL_CAPTION); + } else { + const RailTypeInfo *rti = GetRailTypeInfo((RailType)FindFirstBit(this->filter.railtypes)); + SetDParam(0, rti->strings.build_caption); + } } else if (this->vehicle_type == VEH_ROAD && !this->listview_mode) { - const RoadTypeInfo *rti = GetRoadTypeInfo(this->filter.roadtype); - SetDParam(0, rti->strings.build_caption); + uint num_roadtypes = CountBits(this->filter.roadtypes); + if (num_roadtypes != 1) { + SetDParam(0, STR_BUY_VEHICLE_ROAD_VEHICLE_CAPTION); + } else { + const RoadTypeInfo *rti = GetRoadTypeInfo((RoadType)FindFirstBit(this->filter.roadtypes)); + SetDParam(0, rti->strings.build_caption); + } } else { SetDParam(0, (this->listview_mode ? STR_VEHICLE_LIST_AVAILABLE_TRAINS : STR_BUY_VEHICLE_TRAIN_ALL_CAPTION) + this->vehicle_type); } @@ -1930,17 +1953,11 @@ static WindowDesc _build_vehicle_desc( &BuildVehicleWindow::hotkeys ); -void ShowBuildVehicleWindow(TileIndex tile, VehicleType type) +void ShowBuildVehicleWindow(DepotID depot_id, VehicleType type) { - /* We want to be able to open both Available Train as Available Ships, - * so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number. - * As it always is a low value, it won't collide with any real tile - * number. */ - uint num = (tile == INVALID_TILE) ? (int)type : tile.base(); - assert(IsCompanyBuildableVehicleType(type)); - CloseWindowById(WC_BUILD_VEHICLE, num); + CloseWindowById(WC_BUILD_VEHICLE, GetBuildVehicleWindowNumber(depot_id, type)); - new BuildVehicleWindow(_build_vehicle_desc, tile, type); + new BuildVehicleWindow(_build_vehicle_desc, depot_id, type); } diff --git a/src/command_type.h b/src/command_type.h index 2fd494b5ca..467c0fc2b2 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -193,6 +193,7 @@ enum Commands : uint16_t { CMD_BUILD_BRIDGE, ///< build a bridge CMD_BUILD_RAIL_STATION, ///< build a rail station CMD_BUILD_TRAIN_DEPOT, ///< build a train depot + CMD_REMOVE_TRAIN_DEPOT, ///< remove a train depot CMD_BUILD_SINGLE_SIGNAL, ///< build a signal CMD_REMOVE_SINGLE_SIGNAL, ///< remove a signal CMD_TERRAFORM_LAND, ///< terraform a tile diff --git a/src/depot.cpp b/src/depot.cpp index 13317e8a35..0ba6ee3cd3 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -15,9 +15,13 @@ #include "core/pool_func.hpp" #include "vehicle_gui.h" #include "vehiclelist.h" +#include "command_func.h" +#include "vehicle_base.h" #include "safeguards.h" +#include "table/strings.h" + /** All our depots tucked away in a pool. */ DepotPool _depot_pool("Depot"); INSTANTIATE_POOL_METHODS(Depot) @@ -29,21 +33,146 @@ Depot::~Depot() { if (CleaningPool()) return; - if (!IsDepotTile(this->xy) || GetDepotIndex(this->xy) != this->index) { - /* It can happen there is no depot here anymore (TTO/TTD savegames) */ + if (this->owner == INVALID_OWNER) { + /* Deleting depot remnants of TTD savegames while saveload conversion. */ + assert(this->veh_type == VEH_INVALID); return; } /* Clear the order backup. */ - OrderBackup::Reset(this->xy, false); + OrderBackup::Reset(this->index, false); + + /* Make sure no vehicle is going to the old depot. */ + for (Vehicle *v : Vehicle::Iterate()) { + if (v->First() != v) continue; + if (!v->current_order.IsType(OT_GOTO_DEPOT)) continue; + if (v->current_order.GetDestination() != this->index) continue; + + v->current_order.MakeDummy(); + } /* Clear the depot from all order-lists */ RemoveOrderFromAllVehicles(OT_GOTO_DEPOT, this->index); /* Delete the depot-window */ - CloseWindowById(WC_VEHICLE_DEPOT, this->xy); + CloseWindowById(WC_VEHICLE_DEPOT, this->index); /* Delete the depot list */ - VehicleType vt = GetDepotVehicleType(this->xy); - CloseWindowById(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_DEPOT_LIST, vt, GetTileOwner(this->xy), this->index).Pack()); + CloseWindowById(GetWindowClassForVehicleType(this->veh_type), + VehicleListIdentifier(VL_DEPOT_LIST, + this->veh_type, this->owner, this->index).Pack()); +} + +/** + * Of all the depot parts a depot has, return the best destination for a vehicle. + * @param v The vehicle. + * @param dep The depot vehicle \a v is heading for. + * @return The closest part of depot to vehicle v. + */ +TileIndex Depot::GetBestDepotTile(Vehicle *v) const +{ + assert(this->veh_type == v->type); + TileIndex best_depot = INVALID_TILE; + uint best_distance = UINT_MAX; + + for (const auto &tile : this->depot_tiles) { + uint new_distance = DistanceManhattan(v->tile, tile); + if (new_distance < best_distance) { + best_depot = tile; + best_distance = new_distance; + } + } + + return best_depot; +} + +/** + * Check we can add some tiles to this depot. + * @param ta The affected tile area. + * @return Whether it is possible to add the tiles or an error message. + */ +CommandCost Depot::BeforeAddTiles(TileArea ta) +{ + assert(ta.tile != INVALID_TILE); + + if (this->ta.tile != INVALID_TILE) { + /* Important when the old rect is completely inside the new rect, resp. the old one was empty. */ + ta.Add(this->ta.tile); + ta.Add(TileAddXY(this->ta.tile, this->ta.w - 1, this->ta.h - 1)); + } + + if ((ta.w > _settings_game.depot.depot_spread) || (ta.h > _settings_game.depot.depot_spread)) { + return_cmd_error(STR_ERROR_DEPOT_TOO_SPREAD_OUT); + } + return CommandCost(); +} + +/** + * Add some tiles to this depot and rescan area for depot_tiles. + * @param ta Affected tile area + * @param adding Whether adding or removing depot tiles. + */ +void Depot::AfterAddRemove(TileArea ta, bool adding) +{ + assert(ta.tile != INVALID_TILE); + + if (adding) { + if (this->ta.tile != INVALID_TILE) { + ta.Add(this->ta.tile); + ta.Add(TileAddXY(this->ta.tile, this->ta.w - 1, this->ta.h - 1)); + } + } else { + ta = this->ta; + } + + this->ta.Clear(); + + for (TileIndex tile : ta) { + if (!IsDepotTile(tile)) continue; + if (GetDepotIndex(tile) != this->index) continue; + this->ta.Add(tile); + } + + if (this->ta.tile != INVALID_TILE) { + this->RescanDepotTiles(); + assert(!this->depot_tiles.empty()); + this->xy = this->depot_tiles[0]; + InvalidateWindowData(WC_SELECT_DEPOT, this->veh_type); + } else { + delete this; + } +} + +/** + * Rescan depot_tiles. Done after AfterAddRemove and SaveLoad. + * Updates the tiles of the depot and its railtypes/roadtypes... + */ +void Depot::RescanDepotTiles() +{ + this->depot_tiles.clear(); + RailTypes old_rail_types = this->r_types.rail_types; + this->r_types.rail_types = RAILTYPES_NONE; + + for (TileIndex tile : this->ta) { + if (!IsDepotTile(tile)) continue; + if (GetDepotIndex(tile) != this->index) continue; + this->depot_tiles.push_back(tile); + switch (veh_type) { + case VEH_ROAD: + this->r_types.road_types |= GetPresentRoadTypes(tile); + break; + case VEH_TRAIN: + this->r_types.rail_types |= (RailTypes)(1 << GetRailType(tile)); + break; + case VEH_SHIP: + /* Mark this ship depot has at least one part, so ships can be built. */ + this->r_types.rail_types |= INVALID_RAILTYPES; + break; + default: break; + } + } + + if (old_rail_types != this->r_types.rail_types) { + InvalidateWindowData(WC_BUILD_VEHICLE, this->index, 0, true); + } } diff --git a/src/depot_base.h b/src/depot_base.h index 1d8330fc74..41512153bf 100644 --- a/src/depot_base.h +++ b/src/depot_base.h @@ -13,10 +13,23 @@ #include "depot_map.h" #include "core/pool_type.hpp" #include "timer/timer_game_calendar.h" +#include "rail_type.h" +#include "road_type.h" -typedef Pool DepotPool; +static const DepotID MAX_DEPOTS = 64000; +/** + * For build_vehicle_window, each vehicle type needs its own unique value. + * So we need some special indexes: MAX_DEPOTS + VEH_TYPE_XXX. + * @see GetBuildVehicleWindowNumber + */ +static_assert(MAX_DEPOTS + VEH_COMPANY_END - 1 <= INVALID_DEPOT); + +typedef Pool DepotPool; extern DepotPool _depot_pool; +class CommandCost; +struct Vehicle; + struct Depot : DepotPool::PoolItem<&_depot_pool> { /* DepotID index member of DepotPool is 2 bytes. */ uint16_t town_cn; ///< The N-1th depot for this town (consecutive number) @@ -25,14 +38,35 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> { std::string name; TimerGameCalendar::Date build_date; ///< Date of construction - Depot(TileIndex xy = INVALID_TILE) : xy(xy) {} + VehicleType veh_type; ///< Vehicle type of the depot. + Owner owner; ///< Owner of the depot. + Station *station; ///< For aircraft, station associated with this hangar. + + union { + RoadTypes road_types; + RailTypes rail_types; + } r_types; + + TileArea ta; + std::vector depot_tiles; + + Depot(TileIndex xy = INVALID_TILE, VehicleType type = VEH_INVALID, Owner owner = INVALID_OWNER, Station *station = nullptr) : + xy(xy), + veh_type(type), + owner(owner), + station(station), + ta(xy, 1, 1) {} + ~Depot(); static inline Depot *GetByTile(TileIndex tile) { + assert(Depot::IsValidID(GetDepotIndex(tile))); return Depot::Get(GetDepotIndex(tile)); } + TileIndex GetBestDepotTile(Vehicle *v) const; + /** * Is the "type" of depot the same as the given depot, * i.e. are both a rail, road or ship depots? @@ -43,6 +77,15 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> { { return GetTileType(d->xy) == GetTileType(this->xy); } + + /* Check we can add some tiles to this depot. */ + CommandCost BeforeAddTiles(TileArea ta); + + /* Add some tiles to this depot and rescan area for depot_tiles. */ + void AfterAddRemove(TileArea ta, bool adding); + + /* Rescan depot_tiles. Done after AfterAddRemove and SaveLoad. */ + void RescanDepotTiles(); }; #endif /* DEPOT_BASE_H */ diff --git a/src/depot_cmd.cpp b/src/depot_cmd.cpp index 294de69e32..32cd01558b 100644 --- a/src/depot_cmd.cpp +++ b/src/depot_cmd.cpp @@ -48,7 +48,7 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str Depot *d = Depot::GetIfValid(depot_id); if (d == nullptr) return CMD_ERROR; - CommandCost ret = CheckTileOwnership(d->xy); + CommandCost ret = CheckOwnership(d->owner); if (ret.Failed()) return ret; bool reset = text.empty(); @@ -68,11 +68,76 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str /* Update the orders and depot */ SetWindowClassesDirty(WC_VEHICLE_ORDERS); - SetWindowDirty(WC_VEHICLE_DEPOT, d->xy); + SetWindowDirty(WC_VEHICLE_DEPOT, d->index); /* Update the depot list */ - VehicleType vt = GetDepotVehicleType(d->xy); - SetWindowDirty(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_DEPOT_LIST, vt, GetTileOwner(d->xy), d->index).Pack()); + SetWindowDirty(GetWindowClassForVehicleType(d->veh_type), VehicleListIdentifier(VL_DEPOT_LIST, d->veh_type, d->owner, d->index).Pack()); } return CommandCost(); } + +/** + * Look for or check depot to join to, building a new one if necessary. + * @param ta The area of the new depot. + * @param veh_type The vehicle type of the new depot. + * @param join_to DepotID of the depot to join to. + * If INVALID_DEPOT, look whether it is possible to join to an existing depot. + * If NEW_DEPOT, directly create a new depot. + * @param depot The pointer to the depot. + * @param adjacent Whether adjacent depots are allowed + * @return command cost with the error or 'okay' + */ +CommandCost FindJoiningDepot(TileArea ta, VehicleType veh_type, DepotID &join_to, Depot *&depot, bool adjacent, DoCommandFlag flags) +{ + /* Look for a joining depot if needed. */ + if (join_to == INVALID_DEPOT) { + assert(depot == nullptr); + DepotID closest_depot = INVALID_DEPOT; + + TileArea check_area(ta); + check_area.Expand(1); + + /* Check around to see if there's any depot there. */ + for (TileIndex tile_cur : check_area) { + if (IsValidTile(tile_cur) && IsDepotTile(tile_cur)) { + Depot *d = Depot::GetByTile(tile_cur); + assert(d != nullptr); + if (d->veh_type != veh_type) continue; + if (d->owner != _current_company) continue; + + if (closest_depot == INVALID_DEPOT) { + closest_depot = d->index; + } else if (closest_depot != d->index) { + if (!adjacent) return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING_DEPOT); + } + } + } + + if (closest_depot != INVALID_DEPOT) { + assert(Depot::IsValidID(closest_depot)); + depot = Depot::Get(closest_depot); + } + + join_to = depot == nullptr ? NEW_DEPOT : depot->index; + } + + /* At this point, join_to is NEW_DEPOT or a valid DepotID. */ + + if (join_to == NEW_DEPOT) { + /* New depot needed. */ + if (!Depot::CanAllocateItem()) return CMD_ERROR; + if (flags & DC_EXEC) { + depot = new Depot(ta.tile, veh_type, _current_company); + depot->build_date = TimerGameCalendar::date; + } + } else { + /* Joining depots. */ + assert(Depot::IsValidID(join_to)); + depot = Depot::Get(join_to); + assert(depot->owner == _current_company); + assert(depot->veh_type == veh_type); + return depot->BeforeAddTiles(ta); + } + + return CommandCost(); +} diff --git a/src/depot_func.h b/src/depot_func.h index 208e02110c..f214e6d848 100644 --- a/src/depot_func.h +++ b/src/depot_func.h @@ -10,10 +10,12 @@ #ifndef DEPOT_FUNC_H #define DEPOT_FUNC_H +#include "depot_type.h" #include "vehicle_type.h" #include "slope_func.h" +#include "command_type.h" -void ShowDepotWindow(TileIndex tile, VehicleType type); +void ShowDepotWindow(DepotID depot_id); void InitDepotWindowBlockSizes(); void DeleteDepotHighlightOfVehicle(const Vehicle *v); @@ -33,4 +35,13 @@ inline bool CanBuildDepotByTileh(DiagDirection direction, Slope tileh) return IsSteepSlope(tileh) ? (tileh & entrance_corners) == entrance_corners : (tileh & entrance_corners) != 0; } +struct Depot; +CommandCost FindJoiningDepot(TileArea ta, VehicleType veh_type, DepotID &join_to, Depot *&depot, bool adjacent, DoCommandFlag flags); + +using DepotPickerCmdProc = std::function; +void ShowSelectDepotIfNeeded(TileArea ta, DepotPickerCmdProc proc, VehicleType veh_type); + +struct Window; +void CheckRedrawDepotHighlight(const Window *w, VehicleType veh_type); + #endif /* DEPOT_FUNC_H */ diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp index e62915fe2c..151efe3c23 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -29,9 +29,11 @@ #include "zoom_func.h" #include "error.h" #include "depot_cmd.h" +#include "station_base.h" #include "train_cmd.h" #include "vehicle_cmd.h" #include "core/geometry_func.hpp" +#include "depot_func.h" #include "widgets/depot_widget.h" @@ -78,6 +80,7 @@ static constexpr NWidgetPart _nested_train_depot_widgets[] = { NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_D_BUILD), SetDataTip(0x0, STR_NULL), SetFill(1, 1), SetResize(1, 0), NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_D_CLONE), SetDataTip(0x0, STR_NULL), SetFill(1, 1), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_D_HIGHLIGHT), SetDataTip(STR_BUTTON_HIGHLIGHT_DEPOT, STR_TOOLTIP_HIGHLIGHT_DEPOT), SetFill(1, 1), SetResize(1, 0), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_D_VEHICLE_LIST), SetDataTip(0x0, STR_NULL), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetFill(0, 1), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_D_STOP_ALL), SetDataTip(SPR_FLAG_VEH_STOPPED, STR_NULL), SetAspect(WidgetDimensions::ASPECT_VEHICLE_FLAG), SetFill(0, 1), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_D_START_ALL), SetDataTip(SPR_FLAG_VEH_RUNNING, STR_NULL), SetAspect(WidgetDimensions::ASPECT_VEHICLE_FLAG), SetFill(0, 1), @@ -266,15 +269,17 @@ struct DepotWindow : Window { Scrollbar *hscroll; ///< Only for trains. Scrollbar *vscroll; - DepotWindow(WindowDesc &desc, TileIndex tile, VehicleType type) : Window(desc) + DepotWindow(WindowDesc &desc, DepotID depot_id) : Window(desc) { - assert(IsCompanyBuildableVehicleType(type)); // ensure that we make the call with a valid type + assert(Depot::IsValidID(depot_id)); + Depot *depot = Depot::Get(depot_id); + assert(IsCompanyBuildableVehicleType(depot->veh_type)); this->sel = INVALID_VEHICLE; this->vehicle_over = INVALID_VEHICLE; this->generate_list = true; this->hovered_widget = -1; - this->type = type; + this->type = depot->veh_type; this->num_columns = 1; // for non-trains this gets set in FinishInitNested() this->unitnumber_digits = 2; @@ -282,23 +287,24 @@ struct DepotWindow : Window { this->hscroll = (this->type == VEH_TRAIN ? this->GetScrollbar(WID_D_H_SCROLL) : nullptr); this->vscroll = this->GetScrollbar(WID_D_V_SCROLL); /* Don't show 'rename button' of aircraft hangar */ - this->GetWidget(WID_D_SHOW_RENAME)->SetDisplayedPlane(type == VEH_AIRCRAFT ? SZSP_NONE : 0); + this->GetWidget(WID_D_SHOW_RENAME)->SetDisplayedPlane(this->type == VEH_AIRCRAFT ? SZSP_NONE : 0); /* Only train depots have a horizontal scrollbar and a 'sell chain' button */ - if (type == VEH_TRAIN) this->GetWidget(WID_D_MATRIX)->widget_data = 1 << MAT_COL_START; - this->GetWidget(WID_D_SHOW_H_SCROLL)->SetDisplayedPlane(type == VEH_TRAIN ? 0 : SZSP_HORIZONTAL); - this->GetWidget(WID_D_SHOW_SELL_CHAIN)->SetDisplayedPlane(type == VEH_TRAIN ? 0 : SZSP_NONE); - this->SetupWidgetData(type); - this->FinishInitNested(tile); + if (this->type == VEH_TRAIN) this->GetWidget(WID_D_MATRIX)->widget_data = 1 << MAT_COL_START; + this->GetWidget(WID_D_SHOW_H_SCROLL)->SetDisplayedPlane(this->type == VEH_TRAIN ? 0 : SZSP_HORIZONTAL); + this->GetWidget(WID_D_SHOW_SELL_CHAIN)->SetDisplayedPlane(this->type == VEH_TRAIN ? 0 : SZSP_NONE); + this->SetupWidgetData(this->type); + this->FinishInitNested(depot_id); - this->owner = GetTileOwner(tile); + this->owner = depot->owner; OrderBackup::Reset(); } void Close([[maybe_unused]] int data = 0) override { CloseWindowById(WC_BUILD_VEHICLE, this->window_number); - CloseWindowById(GetWindowClassForVehicleType(this->type), VehicleListIdentifier(VL_DEPOT_LIST, this->type, this->owner, this->GetDepotIndex()).Pack(), false); + CloseWindowById(GetWindowClassForVehicleType(this->type), VehicleListIdentifier(VL_DEPOT_LIST, this->type, this->owner, this->window_number).Pack(), false); OrderBackup::Reset(this->window_number); + SetViewportHighlightDepot(this->window_number, false); this->Window::Close(); } @@ -426,7 +432,7 @@ struct DepotWindow : Window { if (widget != WID_D_CAPTION) return; SetDParam(0, this->type); - SetDParam(1, this->GetDepotIndex()); + SetDParam(1, (this->type == VEH_AIRCRAFT) ? Depot::Get(this->window_number)->station->index : this->window_number); } struct GetDepotVehiclePtData { @@ -708,6 +714,9 @@ struct DepotWindow : Window { void OnPaint() override { + extern DepotID _viewport_highlight_depot; + this->SetWidgetLoweredState(WID_D_HIGHLIGHT, _viewport_highlight_depot == this->window_number); + if (this->generate_list) { /* Generate the vehicle list * It's ok to use the wagon pointers for non-trains as they will be ignored */ @@ -742,8 +751,7 @@ struct DepotWindow : Window { } /* Setup disabled buttons. */ - TileIndex tile = this->window_number; - this->SetWidgetsDisabledState(!IsTileOwner(tile, _local_company), + this->SetWidgetsDisabledState(this->owner != _local_company, WID_D_STOP_ALL, WID_D_START_ALL, WID_D_SELL, @@ -759,6 +767,8 @@ struct DepotWindow : Window { void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override { + TileIndex tile = Depot::Get(this->window_number)->xy; + switch (widget) { case WID_D_MATRIX: // List this->DepotClick(pt.x, pt.y); @@ -789,20 +799,25 @@ struct DepotWindow : Window { if (_ctrl_pressed) { ShowExtraViewportWindow(this->window_number); } else { - ScrollMainWindowToTile(this->window_number); + ScrollMainWindowToTile(tile); } break; case WID_D_RENAME: // Rename button SetDParam(0, this->type); - SetDParam(1, Depot::GetByTile((TileIndex)this->window_number)->index); + SetDParam(1, this->window_number); ShowQueryString(STR_DEPOT_NAME, STR_DEPOT_RENAME_DEPOT_CAPTION, MAX_LENGTH_DEPOT_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS); break; + case WID_D_HIGHLIGHT: + this->SetWidgetDirty(WID_D_HIGHLIGHT); + SetViewportHighlightDepot(this->window_number, !this->IsWidgetLowered(WID_D_HIGHLIGHT)); + break; + case WID_D_STOP_ALL: case WID_D_START_ALL: { VehicleListIdentifier vli(VL_DEPOT_LIST, this->type, this->owner); - Command::Post(this->window_number, widget == WID_D_START_ALL, false, vli); + Command::Post(tile, widget == WID_D_START_ALL, false, vli); break; } @@ -810,7 +825,7 @@ struct DepotWindow : Window { /* Only open the confirmation window if there are anything to sell */ if (!this->vehicle_list.empty() || !this->wagon_list.empty()) { SetDParam(0, this->type); - SetDParam(1, this->GetDepotIndex()); + SetDParam(1, this->window_number); ShowQuery( STR_DEPOT_CAPTION, STR_DEPOT_SELL_CONFIRMATION_TEXT, @@ -821,11 +836,11 @@ struct DepotWindow : Window { break; case WID_D_VEHICLE_LIST: - ShowVehicleListWindow(GetTileOwner(this->window_number), this->type, (TileIndex)this->window_number); + ShowVehicleListWindowDepot(this->owner, this->type, this->window_number); break; case WID_D_AUTOREPLACE: - Command::Post(this->window_number, this->type); + Command::Post(tile, this->type); break; } @@ -836,7 +851,7 @@ struct DepotWindow : Window { if (!str.has_value()) return; /* Do depot renaming */ - Command::Post(STR_ERROR_CAN_T_RENAME_DEPOT, this->GetDepotIndex(), *str); + Command::Post(STR_ERROR_CAN_T_RENAME_DEPOT, this->window_number, *str); } bool OnRightClick([[maybe_unused]] Point pt, WidgetID widget) override @@ -902,10 +917,10 @@ struct DepotWindow : Window { { if (_ctrl_pressed) { /* Share-clone, do not open new viewport, and keep tool active */ - Command::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, this->window_number, v->index, true); + Command::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, Depot::Get(this->window_number)->xy, v->index, true); } else { /* Copy-clone, open viewport for new vehicle, and deselect the tool (assume player wants to change things on new vehicle) */ - if (Command::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, CcCloneVehicle, this->window_number, v->index, false)) { + if (Command::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, CcCloneVehicle, Depot::Get(this->window_number)->xy, v->index, false)) { ResetObjectToPlace(); } } @@ -1111,43 +1126,33 @@ struct DepotWindow : Window { return ES_NOT_HANDLED; } - - /** - * Gets the DepotID of the current window. - * In the case of airports, this is the station ID. - * @return Depot or station ID of this window. - */ - inline uint16_t GetDepotIndex() const - { - return (this->type == VEH_AIRCRAFT) ? ::GetStationIndex(this->window_number) : ::GetDepotIndex(this->window_number); - } }; static void DepotSellAllConfirmationCallback(Window *win, bool confirmed) { if (confirmed) { - DepotWindow *w = (DepotWindow*)win; - TileIndex tile = w->window_number; - VehicleType vehtype = w->type; - Command::Post(tile, vehtype); + assert(Depot::IsValidID(win->window_number)); + Depot *d = Depot::Get(win->window_number); + Command::Post(d->xy, d->veh_type); } } /** - * Opens a depot window - * @param tile The tile where the depot/hangar is located - * @param type The type of vehicles in the depot + * Opens a depot window. + * @param depot_id Index of the depot. */ -void ShowDepotWindow(TileIndex tile, VehicleType type) +void ShowDepotWindow(DepotID depot_id) { - if (BringWindowToFrontById(WC_VEHICLE_DEPOT, tile) != nullptr) return; + assert(Depot::IsValidID(depot_id)); + if (BringWindowToFrontById(WC_VEHICLE_DEPOT, depot_id) != nullptr) return; - switch (type) { + Depot *d = Depot::Get(depot_id); + switch (d->veh_type) { default: NOT_REACHED(); - case VEH_TRAIN: new DepotWindow(_train_depot_desc, tile, type); break; - case VEH_ROAD: new DepotWindow(_road_depot_desc, tile, type); break; - case VEH_SHIP: new DepotWindow(_ship_depot_desc, tile, type); break; - case VEH_AIRCRAFT: new DepotWindow(_aircraft_depot_desc, tile, type); break; + case VEH_TRAIN: new DepotWindow(_train_depot_desc, depot_id); break; + case VEH_ROAD: new DepotWindow(_road_depot_desc, depot_id); break; + case VEH_SHIP: new DepotWindow(_ship_depot_desc, depot_id); break; + case VEH_AIRCRAFT: new DepotWindow(_aircraft_depot_desc, depot_id); break; } } @@ -1164,8 +1169,347 @@ void DeleteDepotHighlightOfVehicle(const Vehicle *v) */ if (_special_mouse_mode != WSM_DRAGDROP) return; - w = dynamic_cast(FindWindowById(WC_VEHICLE_DEPOT, v->tile)); + /* For shadows and rotors, do nothing. */ + if (v->type == VEH_AIRCRAFT && !Aircraft::From(v)->IsNormalAircraft()) return; + + assert(IsDepotTile(v->tile)); + w = dynamic_cast(FindWindowById(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile))); if (w != nullptr) { if (w->sel == v->index) ResetObjectToPlace(); } } + +static std::vector _depots_nearby_list; + +/** Structure with user-data for AddNearbyDepot. */ +struct AddNearbyDepotData { + TileArea search_area; ///< Search area. + VehicleType type; ///< Vehicle type of the searched depots. +}; + +/** + * Add depot on this tile to _depots_nearby_list if it's fully within the + * depot spread. + * @param tile Tile just being checked + * @param user_data Pointer to TileArea context + */ +static bool AddNearbyDepot(TileIndex tile, void *user_data) +{ + AddNearbyDepotData *andd = (AddNearbyDepotData *)user_data; + + /* Check if own depot and if we stay within station spread */ + if (!IsDepotTile(tile)) return false; + Depot *dep = Depot::GetByTile(tile); + if (dep->owner != _local_company || dep->veh_type != andd->type || + (find(_depots_nearby_list.begin(), _depots_nearby_list.end(), dep->index) != _depots_nearby_list.end())) { + return false; + } + + CommandCost cost = dep->BeforeAddTiles(andd->search_area); + if (cost.Succeeded()) { + _depots_nearby_list.push_back(dep->index); + } + + return false; +} + +/** + * Circulate around the to-be-built depot to find depots we could join. + * Make sure that only depots are returned where joining wouldn't exceed + * depot spread and are our own depot. + * @param ta Base tile area of the to-be-built depot + * @param veh_type Vehicle type depots to look for + * @param distant_join Search for adjacent depots (false) or depots fully + * within depot spread + */ +static const Depot *FindDepotsNearby(TileArea ta, VehicleType veh_type, bool distant_join) +{ + _depots_nearby_list.clear(); + _depots_nearby_list.push_back(NEW_DEPOT); + + /* Check the inside, to return, if we sit on another big depot */ + Depot *depot; + for (TileIndex t : ta) { + if (!IsDepotTile(t)) continue; + depot = Depot::GetByTile(t); + if (depot->veh_type == veh_type && depot->owner == _current_company) return depot; + } + + /* Only search tiles where we have a chance to stay within the depot spread. + * The complete check needs to be done in the callback as we don't know the + * extent of the found depot, yet. */ + if (distant_join && std::min(ta.w, ta.h) >= _settings_game.depot.depot_spread) return nullptr; + uint max_dist = distant_join ? _settings_game.depot.depot_spread - std::min(ta.w, ta.h) : 1; + + AddNearbyDepotData andd; + andd.search_area = ta; + andd.type = veh_type; + + TileIndex tile = TileAddByDir(andd.search_area.tile, DIR_N); + CircularTileSearch(&tile, max_dist, ta.w, ta.h, AddNearbyDepot, &andd); + + return nullptr; +} + +/** + * Check whether we need to show the depot selection window. + * @param ta Tile area of the to-be-built depot. + * @param proc The procedure for the depot picker. + * @param veh_type the vehicle type of the depot. + * @return whether we need to show the depot selection window. + */ +static bool DepotJoinerNeeded(TileArea ta, VehicleType veh_type) +{ + /* If a window is already opened and we didn't ctrl-click, + * return true (i.e. just flash the old window) */ + Window *selection_window = FindWindowById(WC_SELECT_DEPOT, veh_type); + if (selection_window != nullptr) { + /* Abort current distant-join and start new one */ + selection_window->Close(); + UpdateTileSelection(); + } + + /* Only show the popup if we press ctrl. */ + if (!_ctrl_pressed) return false; + + /* Test for adjacent depot or depot below selection. + * If adjacent-stations is disabled and we are building next to a depot, do not show the selection window. + * but join the other depot immediately. */ + return FindDepotsNearby(ta, veh_type, false) == nullptr; +} + +/** + * Window for selecting depots to (distant) join to. + */ +struct SelectDepotWindow : Window { + DepotPickerCmdProc select_depot_proc; ///< The procedure params + TileArea area; ///< Location of new depot + Scrollbar *vscroll; ///< Vertical scrollbar for the window + + SelectDepotWindow(WindowDesc &desc, TileArea ta, DepotPickerCmdProc& proc, VehicleType veh_type) : + Window(desc), + select_depot_proc(std::move(proc)), + area(ta) + { + this->CreateNestedTree(); + this->vscroll = this->GetScrollbar(WID_JD_SCROLLBAR); + this->FinishInitNested(veh_type); + this->OnInvalidateData(0); + + _thd.freeze = true; + } + + ~SelectDepotWindow() + { + SetViewportHighlightDepot(INVALID_DEPOT, true); + + _thd.freeze = false; + } + + void UpdateWidgetSize(int widget, Dimension &size, const Dimension &padding, [[maybe_unused]] Dimension &fill, Dimension &resize) override + { + if (widget != WID_JD_PANEL) return; + + resize.height = GetCharacterHeight(FS_NORMAL); + size.height = 5 * resize.height + padding.height; + + /* Determine the widest string. */ + Dimension d = GetStringBoundingBox(STR_JOIN_DEPOT_CREATE_SPLITTED_DEPOT); + for (const auto &depot : _depots_nearby_list) { + if (depot == NEW_DEPOT) continue; + const Depot *dep = Depot::Get(depot); + SetDParam(0, this->window_number); + SetDParam(1, dep->index); + d = maxdim(d, GetStringBoundingBox(STR_DEPOT_LIST_DEPOT)); + } + + d.height = 5 * resize.height; + d.width += padding.width; + d.height += padding.height; + size = d; + } + + void DrawWidget(const Rect &r, int widget) const override + { + if (widget != WID_JD_PANEL) return; + + Rect tr = r.Shrink(WidgetDimensions::scaled.framerect); + + auto [first, last] = this->vscroll->GetVisibleRangeIterators(_depots_nearby_list); + for (auto it = first; it != last; ++it, tr.top += this->resize.step_height) { + if (*it == NEW_DEPOT) { + DrawString(tr, STR_JOIN_DEPOT_CREATE_SPLITTED_DEPOT); + } else { + SetDParam(0, this->window_number); + SetDParam(1, *it); + [[maybe_unused]] Depot *depot = Depot::GetIfValid(*it); + assert(depot != nullptr); + DrawString(tr, STR_DEPOT_LIST_DEPOT); + } + } + } + + void OnClick(Point pt, int widget, [[maybe_unused]] int click_count) override + { + if (widget != WID_JD_PANEL) return; + + auto it = this->vscroll->GetScrolledItemFromWidget(_depots_nearby_list, pt.y, this, WID_JD_PANEL, WidgetDimensions::scaled.framerect.top); + if (it == _depots_nearby_list.end()) return; + + /* Execute stored Command */ + this->select_depot_proc(*it); + + InvalidateWindowData(WC_SELECT_DEPOT, window_number); + this->Close(); + } + + void OnRealtimeTick([[maybe_unused]] uint delta_ms) override + { + if (_thd.dirty & 2) { + _thd.dirty &= ~2; + this->SetDirty(); + } + } + + void OnResize() override + { + this->vscroll->SetCapacityFromWidget(this, WID_JD_PANEL, WidgetDimensions::scaled.framerect.Vertical()); + } + + /** + * Some data on this window has become invalid. + * @param data Information about the changed data. + * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details. + */ + void OnInvalidateData([[maybe_unused]] int data = 0, bool gui_scope = true) override + { + if (!gui_scope) return; + FindDepotsNearby(this->area, (VehicleType)this->window_number, true); + this->vscroll->SetCount((uint)_depots_nearby_list.size()); + this->SetDirty(); + } + + void OnMouseOver(Point pt, int widget) override + { + if (widget != WID_JD_PANEL) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + return; + } + + /* Highlight depot under cursor */ + auto it = this->vscroll->GetScrolledItemFromWidget(_depots_nearby_list, pt.y, this, WID_JD_PANEL, WidgetDimensions::scaled.framerect.top); + SetViewportHighlightDepot(*it, true); + } + +}; + +static const NWidgetPart _nested_select_depot_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_JD_CAPTION), SetDataTip(STR_JOIN_DEPOT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_JD_PANEL), SetResize(1, 0), SetScrollbar(WID_JD_SCROLLBAR), EndContainer(), + NWidget(NWID_VERTICAL), + NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_JD_SCROLLBAR), + NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _select_depot_desc( + WDP_AUTO, "build_depot_join", 200, 180, + WC_SELECT_DEPOT, WC_NONE, + WDF_CONSTRUCTION, + _nested_select_depot_widgets +); + +/** + * Show the depot selection window when needed. If not, build the depot. + * @param ta Area to build the depot in. + * @param proc Details of the procedure for the depot picker. + * @param veh_type Vehicle type of the depot to be built. + */ +void ShowSelectDepotIfNeeded(TileArea ta, DepotPickerCmdProc proc, VehicleType veh_type) +{ + if (DepotJoinerNeeded(ta, veh_type)) { + if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); + new SelectDepotWindow(_select_depot_desc, ta, proc, veh_type); + } else { + proc(INVALID_DEPOT); + } +} + +/** + * Find depots adjacent to the current tile highlight area, so that all depot tiles + * can be highlighted. + * @param v_type Vehicle type to check. + */ +static void HighlightSingleAdjacentDepot(VehicleType v_type) +{ + /* With distant join we don't know which depot will be selected, so don't show any */ + if (_ctrl_pressed) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + return; + } + + /* Tile area for TileHighlightData */ + TileArea location(TileVirtXY(_thd.pos.x, _thd.pos.y), _thd.size.x / TILE_SIZE - 1, _thd.size.y / TILE_SIZE - 1); + + /* If the current tile is already a depot, then it must be the nearest depot. */ + if (IsDepotTypeTile(location.tile, (TransportType)v_type) && + GetTileOwner(location.tile) == _local_company) { + SetViewportHighlightDepot(GetDepotIndex(location.tile), true); + return; + } + + /* Extended area by one tile */ + uint x = TileX(location.tile); + uint y = TileY(location.tile); + + int max_c = 1; + TileArea ta(TileXY(std::max(0, x - max_c), std::max(0, y - max_c)), TileXY(std::min(Map::MaxX(), x + location.w + max_c), std::min(Map::MaxY(), y + location.h + max_c))); + + DepotID adjacent = INVALID_DEPOT; + + for (TileIndex tile : ta) { + if (IsDepotTile(tile) && GetTileOwner(tile) == _local_company) { + Depot *depot = Depot::GetByTile(tile); + if (depot == nullptr) continue; + if (depot->veh_type != v_type) continue; + if (adjacent != INVALID_DEPOT && depot->index != adjacent) { + /* Multiple nearby, distant join is required. */ + adjacent = INVALID_DEPOT; + break; + } + adjacent = depot->index; + } + } + SetViewportHighlightDepot(adjacent, true); +} + +/** + * Check whether we need to redraw the depot highlight. + * If it is needed actually make the window for redrawing. + * @param w the window to check. + * @param veh_type vehicle type to check. + */ +void CheckRedrawDepotHighlight(const Window *w, VehicleType veh_type) +{ + /* Test if ctrl state changed */ + static bool _last_ctrl_pressed; + if (_ctrl_pressed != _last_ctrl_pressed) { + _thd.dirty = 0xff; + _last_ctrl_pressed = _ctrl_pressed; + } + + if (_thd.dirty & 1) { + _thd.dirty &= ~1; + w->SetDirty(); + + if (_thd.drawstyle == HT_RECT) { + HighlightSingleAdjacentDepot(veh_type); + } + } +} diff --git a/src/depot_map.h b/src/depot_map.h index 87bd915431..78a0f0273c 100644 --- a/src/depot_map.h +++ b/src/depot_map.h @@ -43,16 +43,21 @@ inline bool IsDepotTile(Tile tile) return IsRailDepotTile(tile) || IsRoadDepotTile(tile) || IsShipDepotTile(tile) || IsHangarTile(tile); } +extern DepotID GetHangarIndex(TileIndex t); + /** * Get the index of which depot is attached to the tile. * @param t the tile - * @pre IsRailDepotTile(t) || IsRoadDepotTile(t) || IsShipDepotTile(t) + * @pre IsDepotTile(t) * @return DepotID */ inline DepotID GetDepotIndex(Tile t) { - /* Hangars don't have a Depot class, thus store no DepotID. */ - assert(IsRailDepotTile(t) || IsRoadDepotTile(t) || IsShipDepotTile(t)); + assert(IsDepotTile(t)); + + /* Hangars don't store depot id on m2. */ + if (IsTileType(t, MP_STATION)) return GetHangarIndex(t); + return t.m2(); } diff --git a/src/depot_type.h b/src/depot_type.h index 4e61c1bcbd..a03a33b4f8 100644 --- a/src/depot_type.h +++ b/src/depot_type.h @@ -14,7 +14,10 @@ typedef uint16_t DepotID; ///< Type for the unique identifier of depots. struct Depot; static const DepotID INVALID_DEPOT = UINT16_MAX; +static const DepotID NEW_DEPOT = INVALID_DEPOT - 1; static const uint MAX_LENGTH_DEPOT_NAME_CHARS = 32; ///< The maximum length of a depot name in characters including '\0' +static const uint DEF_MAX_DEPOT_SPREAD = 12; + #endif /* DEPOT_TYPE_H */ diff --git a/src/dock_gui.cpp b/src/dock_gui.cpp index ffbf5aa63e..2cc37266a5 100644 --- a/src/dock_gui.cpp +++ b/src/dock_gui.cpp @@ -32,6 +32,7 @@ #include "waypoint_cmd.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "depot_func.h" #include "widgets/dock_widget.h" @@ -112,6 +113,8 @@ struct BuildDocksToolbarWindow : Window { { if (_game_mode == GM_NORMAL && this->IsWidgetLowered(WID_DT_STATION)) SetViewportCatchmentStation(nullptr, true); if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false); + if (_game_mode == GM_NORMAL && this->IsWidgetLowered(WID_DT_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); + this->Window::Close(); } @@ -164,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 @@ -204,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. */ @@ -260,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; } @@ -270,11 +291,14 @@ struct BuildDocksToolbarWindow : Window { { if (_game_mode != GM_EDITOR && this->IsWidgetLowered(WID_DT_STATION)) SetViewportCatchmentStation(nullptr, true); + if (_game_mode != GM_EDITOR && this->IsWidgetLowered(WID_DT_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); + this->RaiseButtons(); CloseWindowById(WC_BUILD_STATION, TRANSPORT_WATER); CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_WATER); CloseWindowById(WC_SELECT_STATION, 0); + CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP); CloseWindowByClass(WC_BUILD_BRIDGE); } @@ -514,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; } } @@ -529,6 +556,13 @@ public: UpdateDocksDirection(); } + void Close([[maybe_unused]] int data = 0) override + { + CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP); + VpResetFixedSize(); + this->PickerWindowBase::Close(); + } + void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override { switch (widget) { @@ -569,6 +603,7 @@ public: switch (widget) { case WID_BDD_X: case WID_BDD_Y: + CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP); this->RaiseWidget(WID_BDD_X + _ship_depot_direction); _ship_depot_direction = (widget == WID_BDD_X ? AXIS_X : AXIS_Y); this->LowerWidget(WID_BDD_X + _ship_depot_direction); @@ -578,6 +613,11 @@ public: break; } } + + void OnRealtimeTick([[maybe_unused]] uint delta_ms) override + { + CheckRedrawDepotHighlight(this, VEH_SHIP); + } }; static constexpr NWidgetPart _nested_build_docks_depot_widgets[] = { diff --git a/src/economy.cpp b/src/economy.cpp index cd99094239..5c0d4f7bec 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -55,6 +55,7 @@ #include "timer/timer.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" +#include "depot_base.h" #include "table/strings.h" #include "table/pricebase.h" @@ -374,6 +375,12 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner) } if (new_owner == INVALID_OWNER) RebuildSubsidisedSourceAndDestinationCache(); + for (Depot *dep : Depot::Iterate()) { + if (dep->owner == old_owner && new_owner != INVALID_OWNER) { + dep->owner = new_owner; + } + } + /* Take care of rating and transport rights in towns */ for (Town *t : Town::Iterate()) { /* If a company takes over, give the ratings to that company. */ diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp index 96aa2d1c9f..3930c81469 100644 --- a/src/group_cmd.cpp +++ b/src/group_cmd.cpp @@ -19,6 +19,7 @@ #include "core/pool_func.hpp" #include "order_backup.h" #include "group_cmd.h" +#include "depot_map.h" #include "table/strings.h" @@ -579,7 +580,7 @@ std::tuple CmdAddVehicleGroup(DoCommandFlag flags, GroupID } } - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + if (IsDepotTypeTile(v->tile, (TransportType)v->type)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); SetWindowDirty(WC_VEHICLE_VIEW, v->index); SetWindowDirty(WC_VEHICLE_DETAILS, v->index); InvalidateWindowData(WC_VEHICLE_VIEW, v->index); diff --git a/src/group_gui.cpp b/src/group_gui.cpp index bb2a8d840d..ff3244c3a8 100644 --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -842,7 +842,7 @@ public: break; case WID_GL_AVAILABLE_VEHICLES: - ShowBuildVehicleWindow(INVALID_TILE, this->vli.vtype); + ShowBuildVehicleWindow(INVALID_DEPOT, this->vli.vtype); break; case WID_GL_MANAGE_VEHICLES_DROPDOWN: { diff --git a/src/lang/english.txt b/src/lang/english.txt index 87a8d3c594..b00cb37577 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -278,6 +278,8 @@ STR_TOOLTIP_FILTER_CRITERIA :{BLACK}Select f STR_BUTTON_SORT_BY :{BLACK}Sort by STR_BUTTON_CATCHMENT :{BLACK}Coverage STR_TOOLTIP_CATCHMENT :{BLACK}Toggle coverage area display +STR_BUTTON_HIGHLIGHT_DEPOT :{BLACK}Highlight +STR_TOOLTIP_HIGHLIGHT_DEPOT :{BLACK}Toggle highlight on viewport for this depot STR_TOOLTIP_CLOSE_WINDOW :{BLACK}Close window STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS :{BLACK}Window title - drag this to move window @@ -1462,6 +1464,7 @@ STR_CONFIG_SETTING_STOP_ON_TOWN_ROAD_HELPTEXT :Allow construct STR_CONFIG_SETTING_STOP_ON_COMPETITOR_ROAD :Allow drive-through road stops on roads owned by competitors: {STRING2} STR_CONFIG_SETTING_STOP_ON_COMPETITOR_ROAD_HELPTEXT :Allow construction of drive-through road stops on roads owned by other companies STR_CONFIG_SETTING_DYNAMIC_ENGINES_EXISTING_VEHICLES :{WHITE}Changing this setting is not possible when there are vehicles +STR_CONFIG_SETTING_REPLACEMENTS_DIFF_TYPE :{WHITE}Disabling this setting is not possible during a game STR_CONFIG_SETTING_INFRASTRUCTURE_MAINTENANCE :Infrastructure maintenance: {STRING2} STR_CONFIG_SETTING_INFRASTRUCTURE_MAINTENANCE_HELPTEXT :When enabled, infrastructure causes maintenance costs. The cost grows over-proportional with the network size, thus affecting bigger companies more than smaller ones @@ -1615,6 +1618,15 @@ STR_CONFIG_SETTING_SE_FLAT_WORLD_HEIGHT :The height leve STR_CONFIG_SETTING_EDGES_NOT_EMPTY :{WHITE}One or more tiles at the northern edge are not empty STR_CONFIG_SETTING_EDGES_NOT_WATER :{WHITE}One or more tiles at one of the edges is not water +STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS :Allow to join depot parts not directly adjacent: {STRING2} +STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS_HELPTEXT :Allow adding parts to a depot without directly touching the existing parts. Needs Ctrl+Click while placing the new parts +STR_CONFIG_SETTING_DEPOT_SPREAD :Maximum depot spread: {STRING2} +STR_CONFIG_SETTING_DEPOT_SPREAD_HELPTEXT :Maximum area the parts of a single depot may be spread out on +STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_RAIL :Allow replacing rail vehicles with incompatible rail types: {STRING2} +STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_RAIL_HELPTEXT :Allow replacing rail vehicles even if they are not compatible by rail type +STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_ROAD :Allow replacing road vehicles with incompatible road types: {STRING2} +STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_ROAD_HELPTEXT :Allow replacing road vehicles even if they are not compatible by road type + STR_CONFIG_SETTING_STATION_SPREAD :Maximum station spread: {STRING2} STR_CONFIG_SETTING_STATION_SPREAD_HELPTEXT :Maximum area the parts of a single station may be spread out on. Note that high values will slow the game @@ -2137,6 +2149,7 @@ STR_CONFIG_SETTING_ENVIRONMENT_TREES :Trees STR_CONFIG_SETTING_AI :Competitors STR_CONFIG_SETTING_AI_NPC :Computer players STR_CONFIG_SETTING_NETWORK :Network +STR_CONFIG_SETTING_DEPOTS :Depots STR_CONFIG_SETTING_REVERSE_AT_SIGNALS :Automatic reversing at signals: {STRING2} STR_CONFIG_SETTING_REVERSE_AT_SIGNALS_HELPTEXT :Allow trains to reverse on a signal, if they waited there a long time @@ -2763,6 +2776,11 @@ STR_JOIN_STATION_CREATE_SPLITTED_STATION :{YELLOW}Build a STR_JOIN_WAYPOINT_CAPTION :{WHITE}Join waypoint STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT :{YELLOW}Build a separate waypoint +# Join depot window +STR_JOIN_DEPOT_CAPTION :{WHITE}Join depot +STR_JOIN_DEPOT_CREATE_SPLITTED_DEPOT :{YELLOW}Build a separate depot +STR_DEPOT_LIST_DEPOT :{YELLOW}{DEPOT} + # Generic toolbar STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE :{BLACK}Disabled as currently no vehicles are available for this infrastructure @@ -5110,10 +5128,12 @@ STR_ERROR_BUOY_IS_IN_USE :{WHITE}... buoy # Depot related errors STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT :{WHITE}Can't build train depot here... +STR_ERROR_CAN_T_REMOVE_TRAIN_DEPOT :{WHITE}Can't remove train depot here... STR_ERROR_CAN_T_BUILD_ROAD_DEPOT :{WHITE}Can't build road vehicle depot here... STR_ERROR_CAN_T_BUILD_TRAM_DEPOT :{WHITE}Can't build tram vehicle depot here... STR_ERROR_CAN_T_BUILD_SHIP_DEPOT :{WHITE}Can't build ship depot here... +STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING_DEPOT :{WHITE}... adjoins more than one existing depot STR_ERROR_CAN_T_RENAME_DEPOT :{WHITE}Can't rename depot... STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT :{WHITE}... must be stopped inside a depot @@ -5123,6 +5143,7 @@ STR_ERROR_AIRCRAFT_MUST_BE_STOPPED_INSIDE_HANGAR :{WHITE}... must STR_ERROR_TRAINS_CAN_ONLY_BE_ALTERED_INSIDE_A_DEPOT :{WHITE}Trains can only be altered when stopped inside a depot STR_ERROR_TRAIN_TOO_LONG :{WHITE}Train too long +STR_ERROR_INCOMPATIBLE_RAILTYPES_WITH_DEPOT :{WHITE}Train chain is incompatible with any tile of this depot STR_ERROR_CAN_T_REVERSE_DIRECTION_RAIL_VEHICLE :{WHITE}Can't reverse direction of vehicle... STR_ERROR_CAN_T_REVERSE_DIRECTION_RAIL_VEHICLE_MULTIPLE_UNITS :{WHITE}... consists of multiple units STR_ERROR_INCOMPATIBLE_RAIL_TYPES :Incompatible rail types @@ -5131,7 +5152,9 @@ STR_ERROR_CAN_T_MOVE_VEHICLE :{WHITE}Can't mo STR_ERROR_REAR_ENGINE_FOLLOW_FRONT :{WHITE}The rear engine will always follow its front counterpart STR_ERROR_UNABLE_TO_FIND_ROUTE_TO :{WHITE}Unable to find route to local depot STR_ERROR_UNABLE_TO_FIND_LOCAL_DEPOT :{WHITE}Unable to find local depot +STR_ERROR_UNABLE_TO_FIND_APPROPRIATE_DEPOT_TILE :{WHITE}Unable to find appropriate depot tile +STR_ERROR_DEPOT_TOO_SPREAD_OUT :{WHITE}... depot too spread out STR_ERROR_DEPOT_WRONG_DEPOT_TYPE :Wrong depot type # Depot unbunching related errors diff --git a/src/order_backup.cpp b/src/order_backup.cpp index f696d8435d..053c407c8c 100644 --- a/src/order_backup.cpp +++ b/src/order_backup.cpp @@ -19,6 +19,8 @@ #include "order_cmd.h" #include "group_cmd.h" #include "vehicle_func.h" +#include "depot_map.h" +#include "depot_base.h" #include "safeguards.h" @@ -45,8 +47,9 @@ OrderBackup::~OrderBackup() */ OrderBackup::OrderBackup(const Vehicle *v, uint32_t user) { + assert(IsDepotTile(v->tile)); this->user = user; - this->tile = v->tile; + this->depot_id = GetDepotIndex(v->tile); this->group = v->group_id; this->CopyConsistPropertiesFrom(v); @@ -123,8 +126,10 @@ void OrderBackup::DoRestore(Vehicle *v) */ /* static */ void OrderBackup::Restore(Vehicle *v, uint32_t user) { + assert(IsDepotTile(v->tile)); + DepotID depot_id_veh = GetDepotIndex(v->tile); for (OrderBackup *ob : OrderBackup::Iterate()) { - if (v->tile != ob->tile || ob->user != user) continue; + if (depot_id_veh != ob->depot_id || ob->user != user) continue; ob->DoRestore(v); delete ob; @@ -133,28 +138,28 @@ void OrderBackup::DoRestore(Vehicle *v) /** * Reset an OrderBackup given a tile and user. - * @param tile The tile associated with the OrderBackup. + * @param depot_id The depot associated with the OrderBackup. * @param user The user associated with the OrderBackup. * @note Must not be used from the GUI! */ -/* static */ void OrderBackup::ResetOfUser(TileIndex tile, uint32_t user) +/* static */ void OrderBackup::ResetOfUser(DepotID depot_id, uint32_t user) { for (OrderBackup *ob : OrderBackup::Iterate()) { - if (ob->user == user && (ob->tile == tile || tile == INVALID_TILE)) delete ob; + if (ob->user == user && (ob->depot_id == depot_id || depot_id == INVALID_DEPOT)) delete ob; } } /** * Clear an OrderBackup * @param flags For command. - * @param tile Tile related to the to-be-cleared OrderBackup. + * @param depot_id Tile related to the to-be-cleared OrderBackup. * @param user_id User that had the OrderBackup. * @return The cost of this operation or an error. */ -CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID user_id) +CommandCost CmdClearOrderBackup(DoCommandFlag flags, DepotID depot_id, ClientID user_id) { - /* No need to check anything. If the tile or user don't exist we just ignore it. */ - if (flags & DC_EXEC) OrderBackup::ResetOfUser(tile == 0 ? INVALID_TILE : tile, user_id); + assert(Depot::IsValidID(depot_id)); + if (flags & DC_EXEC) OrderBackup::ResetOfUser(depot_id, user_id); return CommandCost(); } @@ -173,18 +178,18 @@ CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID us /* If it's not a backup of us, ignore it. */ if (ob->user != user) continue; - Command::Post(0, static_cast(user)); + Command::Post(ob->depot_id, static_cast(user)); return; } } /** * Reset the OrderBackups from GUI/game logic. - * @param t The tile of the order backup. + * @param depot_id The index of the depot associated to the order backups. * @param from_gui Whether the call came from the GUI, i.e. whether * it must be synced over the network. */ -/* static */ void OrderBackup::Reset(TileIndex t, bool from_gui) +/* static */ void OrderBackup::Reset(DepotID depot_id, bool from_gui) { /* The user has CLIENT_ID_SERVER as default when network play is not active, * but compiled it. A network client has its own variable for the unique @@ -195,16 +200,16 @@ CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID us for (OrderBackup *ob : OrderBackup::Iterate()) { /* If this is a GUI action, and it's not a backup of us, ignore it. */ if (from_gui && ob->user != user) continue; - /* If it's not for our chosen tile either, ignore it. */ - if (t != INVALID_TILE && t != ob->tile) continue; + /* If it's not for our chosen depot either, ignore it. */ + if (depot_id != INVALID_DEPOT && depot_id != ob->depot_id) continue; if (from_gui) { /* We need to circumvent the "prevention" from this command being executed * while the game is paused, so use the internal method. Nor do we want * this command to get its cost estimated when shift is pressed. */ - Command::Unsafe(STR_NULL, nullptr, true, false, ob->tile, CommandTraits::Args{ ob->tile, static_cast(user) }); + Command::Unsafe(STR_NULL, nullptr, true, false, ob->depot_id, CommandTraits::Args{ ob->depot_id, static_cast(user) }); } else { - /* The command came from the game logic, i.e. the clearing of a tile. + /* The command came from the game logic, i.e. the clearing of a depot. * In that case we have no need to actually sync this, just do it. */ delete ob; } @@ -246,18 +251,14 @@ CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID us * Removes an order from all vehicles. Triggers when, say, a station is removed. * @param type The type of the order (OT_GOTO_[STATION|DEPOT|WAYPOINT]). * @param destination The destination. Can be a StationID, DepotID or WaypointID. - * @param hangar Only used for airports in the destination. - * When false, remove airport and hangar orders. - * When true, remove either airport or hangar order. */ -/* static */ void OrderBackup::RemoveOrder(OrderType type, DestinationID destination, bool hangar) +/* static */ void OrderBackup::RemoveOrder(OrderType type, DestinationID destination) { for (OrderBackup *ob : OrderBackup::Iterate()) { for (Order *order = ob->orders; order != nullptr; order = order->next) { OrderType ot = order->GetType(); if (ot == OT_GOTO_DEPOT && (order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0) continue; - if (ot == OT_GOTO_DEPOT && hangar && !IsHangarTile(ob->tile)) continue; // Not an aircraft? Can't have a hangar order. - if (ot == OT_IMPLICIT || (IsHangarTile(ob->tile) && ot == OT_GOTO_DEPOT && !hangar)) ot = OT_GOTO_STATION; + if (ot == OT_IMPLICIT) ot = OT_GOTO_STATION; if (ot == type && order->GetDestination() == destination) { /* Remove the order backup! If a station/depot gets removed, we can't/shouldn't restore those broken orders. */ delete ob; diff --git a/src/order_backup.h b/src/order_backup.h index 8616c564ee..823e74b2f3 100644 --- a/src/order_backup.h +++ b/src/order_backup.h @@ -16,6 +16,7 @@ #include "vehicle_type.h" #include "base_consist.h" #include "saveload/saveload.h" +#include "depot_type.h" /** Unique identifier for an order backup. */ typedef uint8_t OrderBackupID; @@ -34,8 +35,8 @@ struct OrderBackup : OrderBackupPool::PoolItem<&_order_backup_pool>, BaseConsist private: friend SaveLoadTable GetOrderBackupDescription(); ///< Saving and loading of order backups. friend struct BKORChunkHandler; ///< Creating empty orders upon savegame loading. - uint32_t user; ///< The user that requested the backup. - TileIndex tile; ///< Tile of the depot where the order was changed. + uint32_t user; ///< The user that requested the backup. + DepotID depot_id; ///< Depot where the order was changed. GroupID group; ///< The group the vehicle was part of. const Vehicle *clone; ///< Vehicle this vehicle was a clone of. @@ -53,13 +54,13 @@ public: static void Backup(const Vehicle *v, uint32_t user); static void Restore(Vehicle *v, uint32_t user); - static void ResetOfUser(TileIndex tile, uint32_t user); + static void ResetOfUser(DepotID depot_id, uint32_t user); static void ResetUser(uint32_t user); - static void Reset(TileIndex tile = INVALID_TILE, bool from_gui = true); + static void Reset(DepotID depot = INVALID_DEPOT, bool from_gui = true); static void ClearGroup(GroupID group); static void ClearVehicle(const Vehicle *v); - static void RemoveOrder(OrderType type, DestinationID destination, bool hangar); + static void RemoveOrder(OrderType type, DestinationID destination); }; #endif /* ORDER_BACKUP_H */ diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 1d6743106e..203d7784f2 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -650,7 +650,7 @@ TileIndex Order::GetLocation(const Vehicle *v, bool airport) const case OT_GOTO_DEPOT: if (this->GetDestination() == INVALID_DEPOT) return INVALID_TILE; - return (v->type == VEH_AIRCRAFT) ? Station::Get(this->GetDestination())->xy : Depot::Get(this->GetDestination())->xy; + return Depot::Get(this->GetDestination())->xy; default: return INVALID_TILE; @@ -684,6 +684,28 @@ uint GetOrderDistance(const Order *prev, const Order *cur, const Vehicle *v, int return v->type == VEH_AIRCRAFT ? DistanceSquare(prev_tile, cur_tile) : DistanceManhattan(prev_tile, cur_tile); } +/** + * Get the station or depot index associated to an order of a vehicle. + * For aircraft, it will return the index of the associated station, even for go to hangar orders. + * @param o Order to check. + * @param is_aircraft Whether the order is of an aircraft vehicle. + * @return index associated to a station or depot, or INVALID_STATION. + */ +DestinationID GetTargetDestination(const Order &o, bool is_aircraft) +{ + DestinationID destination_id = o.GetDestination(); + switch (o.GetType()) { + case OT_GOTO_STATION: + return destination_id; + case OT_GOTO_DEPOT: + assert(Depot::IsValidID(destination_id)); + return is_aircraft ? GetStationIndex(Depot::Get(destination_id)->xy) : destination_id; + default: + return INVALID_STATION; + } +} + + /** * Add an order to the orderlist of a vehicle. * @param flags operation to perform @@ -763,41 +785,15 @@ CommandCost CmdInsertOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID se case OT_GOTO_DEPOT: { if ((new_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) == 0) { - if (v->type == VEH_AIRCRAFT) { - const Station *st = Station::GetIfValid(new_order.GetDestination()); + const Depot *dp = Depot::GetIfValid(new_order.GetDestination()); - if (st == nullptr) return CMD_ERROR; + if (dp == nullptr) return CMD_ERROR; - ret = CheckOwnership(st->owner); - if (ret.Failed()) return ret; + ret = CheckOwnership(dp->owner); + if (ret.Failed()) return ret; - if (!CanVehicleUseStation(v, st) || !st->airport.HasHangar()) { - return CMD_ERROR; - } - } else { - const Depot *dp = Depot::GetIfValid(new_order.GetDestination()); - - if (dp == nullptr) return CMD_ERROR; - - ret = CheckOwnership(GetTileOwner(dp->xy)); - if (ret.Failed()) return ret; - - switch (v->type) { - case VEH_TRAIN: - if (!IsRailDepotTile(dp->xy)) return CMD_ERROR; - break; - - case VEH_ROAD: - if (!IsRoadDepotTile(dp->xy)) return CMD_ERROR; - break; - - case VEH_SHIP: - if (!IsShipDepotTile(dp->xy)) return CMD_ERROR; - break; - - default: return CMD_ERROR; - } - } + if (v->type != dp->veh_type) return CMD_ERROR; + if (v->type == VEH_AIRCRAFT && !CanVehicleUseStation(v, Station::GetByTile(dp->xy))) return CMD_ERROR; } if (new_order.GetNonStopType() != ONSF_STOP_EVERYWHERE && !v->IsGroundVehicle()) return CMD_ERROR; @@ -1780,24 +1776,11 @@ void CheckOrders(const Vehicle *v) * Removes an order from all vehicles. Triggers when, say, a station is removed. * @param type The type of the order (OT_GOTO_[STATION|DEPOT|WAYPOINT]). * @param destination The destination. Can be a StationID, DepotID or WaypointID. - * @param hangar Only used for airports in the destination. - * When false, remove airport and hangar orders. - * When true, remove either airport or hangar order. */ -void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination, bool hangar) +void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination) { - /* Aircraft have StationIDs for depot orders and never use DepotIDs - * This fact is handled specially below - */ - /* Go through all vehicles */ for (Vehicle *v : Vehicle::Iterate()) { - if ((v->type == VEH_AIRCRAFT && v->current_order.IsType(OT_GOTO_DEPOT) && !hangar ? OT_GOTO_STATION : v->current_order.GetType()) == type && - (!hangar || v->type == VEH_AIRCRAFT) && v->current_order.GetDestination() == destination) { - v->current_order.MakeDummy(); - SetWindowDirty(WC_VEHICLE_VIEW, v->index); - } - /* Clear the order from the order-list */ int id = -1; for (Order *order : v->Orders()) { @@ -1806,8 +1789,7 @@ restart: OrderType ot = order->GetType(); if (ot == OT_GOTO_DEPOT && (order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0) continue; - if (ot == OT_GOTO_DEPOT && hangar && v->type != VEH_AIRCRAFT) continue; // Not an aircraft? Can't have a hangar order. - if (ot == OT_IMPLICIT || (v->type == VEH_AIRCRAFT && ot == OT_GOTO_DEPOT && !hangar)) ot = OT_GOTO_STATION; + if (ot == OT_IMPLICIT) ot = OT_GOTO_STATION; if (ot == type && order->GetDestination() == destination) { /* We want to clear implicit orders, but we don't want to make them * dummy orders. They should just vanish. Also check the actual order @@ -1841,7 +1823,7 @@ restart: } } - OrderBackup::RemoveOrder(type, destination, hangar); + OrderBackup::RemoveOrder(type, destination); } /** @@ -2051,10 +2033,11 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool v->IncrementRealOrderIndex(); } else { if (v->type != VEH_AIRCRAFT) { - v->SetDestTile(Depot::Get(order->GetDestination())->xy); + v->SetDestTile(Depot::Get(order->GetDestination())->GetBestDepotTile(v)); } else { Aircraft *a = Aircraft::From(v); - DestinationID destination = a->current_order.GetDestination(); + DestinationID destination_depot = a->current_order.GetDestination(); + StationID destination = GetStationIndex(Depot::Get(destination_depot)->xy); if (a->targetairport != destination) { /* The aircraft is now heading for a different hangar than the next in the orders */ a->SetDestTile(a->GetOrderStationLocation(destination)); diff --git a/src/order_cmd.h b/src/order_cmd.h index b0ab888d94..d59ca58adf 100644 --- a/src/order_cmd.h +++ b/src/order_cmd.h @@ -21,7 +21,7 @@ CommandCost CmdInsertOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID se CommandCost CmdOrderRefit(DoCommandFlag flags, VehicleID veh, VehicleOrderID order_number, CargoID cargo); CommandCost CmdCloneOrder(DoCommandFlag flags, CloneOptions action, VehicleID veh_dst, VehicleID veh_src); CommandCost CmdMoveOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID moving_order, VehicleOrderID target_order); -CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID user_id); +CommandCost CmdClearOrderBackup(DoCommandFlag flags, DepotID depot_id, ClientID user_id); DEF_CMD_TRAIT(CMD_MODIFY_ORDER, CmdModifyOrder, CMD_LOCATION, CMDT_ROUTE_MANAGEMENT) DEF_CMD_TRAIT(CMD_SKIP_TO_ORDER, CmdSkipToOrder, CMD_LOCATION, CMDT_ROUTE_MANAGEMENT) diff --git a/src/order_func.h b/src/order_func.h index 12f7d4684a..797066e1eb 100644 --- a/src/order_func.h +++ b/src/order_func.h @@ -15,7 +15,7 @@ #include "company_type.h" /* Functions */ -void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination, bool hangar = false); +void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination); void InvalidateVehicleOrder(const Vehicle *v, int data); void CheckOrders(const Vehicle*); void DeleteVehicleOrders(Vehicle *v, bool keep_orderlist = false, bool reset_order_indices = true); @@ -23,6 +23,7 @@ bool ProcessOrders(Vehicle *v); bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth = 0, bool pbs_look_ahead = false); VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v); uint GetOrderDistance(const Order *prev, const Order *cur, const Vehicle *v, int conditional_depth = 0); +DestinationID GetTargetDestination(const Order &o, bool is_aircraft); void DrawOrderString(const Vehicle *v, const Order *order, int order_index, int y, bool selected, bool timetable, int left, int middle, int right); diff --git a/src/order_gui.cpp b/src/order_gui.cpp index 77b68d9479..3bcfda6c49 100644 --- a/src/order_gui.cpp +++ b/src/order_gui.cpp @@ -34,6 +34,7 @@ #include "error.h" #include "order_cmd.h" #include "company_cmd.h" +#include "depot_base.h" #include "widgets/order_widget.h" @@ -309,7 +310,7 @@ void DrawOrderString(const Vehicle *v, const Order *order, int order_index, int /* Going to a specific depot. */ SetDParam(0, STR_ORDER_GO_TO_DEPOT_FORMAT); SetDParam(2, v->type); - SetDParam(3, order->GetDestination()); + SetDParam(3, GetTargetDestination(*order, v->type == VEH_AIRCRAFT)); } if (order->GetDepotOrderType() & ODTFB_SERVICE) { @@ -384,8 +385,7 @@ static Order GetOrderCmdFromTile(const Vehicle *v, TileIndex tile) /* check depot first */ if (IsDepotTypeTile(tile, (TransportType)(uint)v->type) && IsTileOwner(tile, _local_company)) { - order.MakeGoToDepot(v->type == VEH_AIRCRAFT ? GetStationIndex(tile) : GetDepotIndex(tile), - ODTFB_PART_OF_ORDERS, + order.MakeGoToDepot(GetDepotIndex(tile), ODTFB_PART_OF_ORDERS, (_settings_client.gui.new_nonstop && v->IsGroundVehicle()) ? ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS : ONSF_STOP_EVERYWHERE); if (_ctrl_pressed) { diff --git a/src/pathfinder/pathfinder_func.h b/src/pathfinder/pathfinder_func.h index 444b100ce7..781d356458 100644 --- a/src/pathfinder/pathfinder_func.h +++ b/src/pathfinder/pathfinder_func.h @@ -12,6 +12,7 @@ #include "../tile_cmd.h" #include "../waypoint_base.h" +#include "../depot_base.h" /** * Calculates the tile of given station that is closest to a given tile @@ -47,6 +48,37 @@ inline TileIndex CalcClosestStationTile(StationID station, TileIndex tile, Stati return TileXY(x, y); } +/** + * Calculates the tile of a depot that is closest to a given tile. + * @param depot_id The depot to calculate the distance to. + * @param tile The tile from where to calculate the distance. + * @return The closest depot tile to the given tile. + */ +static inline TileIndex CalcClosestDepotTile(DepotID depot_id, TileIndex tile) +{ + assert(Depot::IsValidID(depot_id)); + const Depot *dep = Depot::Get(depot_id); + + /* If tile area is empty, use the xy tile. */ + if (dep->ta.tile == INVALID_TILE) { + assert(dep->xy != INVALID_TILE); + return dep->xy; + } + + TileIndex best_tile = INVALID_TILE; + uint best_distance = UINT_MAX; + + for (auto const &depot_tile : dep->depot_tiles) { + uint new_distance = DistanceManhattan(depot_tile, tile); + if (new_distance < best_distance) { + best_tile = depot_tile; + best_distance = new_distance; + } + } + + return best_tile; +} + /** * Wrapper around GetTileTrackStatus() and TrackStatusToTrackdirBits(), as for * single tram bits GetTileTrackStatus() returns 0. The reason for this is diff --git a/src/pathfinder/yapf/yapf_destrail.hpp b/src/pathfinder/yapf/yapf_destrail.hpp index f39a8a2c4b..f6b871ea2c 100644 --- a/src/pathfinder/yapf/yapf_destrail.hpp +++ b/src/pathfinder/yapf/yapf_destrail.hpp @@ -118,6 +118,7 @@ protected: TileIndex m_destTile; TrackdirBits m_destTrackdirs; StationID m_dest_station_id; + DepotID m_dest_depot_id; bool m_any_depot; /** to access inherited path finder */ @@ -149,14 +150,25 @@ public: break; case OT_GOTO_DEPOT: + m_dest_station_id = INVALID_STATION; + if (v->current_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) { m_any_depot = true; + m_dest_depot_id = INVALID_DEPOT; + m_destTile = v->dest_tile; + m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_RAIL, 0)); + } else { + m_dest_depot_id = v->current_order.GetDestination(); + assert(Depot::IsValidID(m_dest_depot_id)); + m_destTile = CalcClosestDepotTile(m_dest_depot_id, v->tile); + m_destTrackdirs = INVALID_TRACKDIR_BIT; } - [[fallthrough]]; + break; default: m_destTile = v->dest_tile; m_dest_station_id = INVALID_STATION; + m_dest_depot_id = INVALID_DEPOT; m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_RAIL, 0)); break; } @@ -176,6 +188,10 @@ public: return HasStationTileRail(tile) && (GetStationIndex(tile) == m_dest_station_id) && (GetRailStationTrack(tile) == TrackdirToTrack(td)); + } else if (m_dest_depot_id != INVALID_DEPOT) { + return IsRailDepotTile(tile) + && (GetDepotIndex(tile) == m_dest_depot_id) + && (GetRailDepotTrack(tile) == TrackdirToTrack(td)); } if (m_any_depot) { diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp index 82fcba1cf1..0f9fb2317b 100644 --- a/src/pathfinder/yapf/yapf_road.cpp +++ b/src/pathfinder/yapf/yapf_road.cpp @@ -234,7 +234,9 @@ protected: TileIndex m_destTile; TrackdirBits m_destTrackdirs; StationID m_dest_station; + DepotID m_dest_depot; StationType m_station_type; + bool m_bus; bool m_non_artic; public: @@ -252,8 +254,14 @@ public: m_destTile = CalcClosestStationTile(m_dest_station, v->tile, m_station_type); m_non_artic = !v->HasArticulatedPart(); m_destTrackdirs = INVALID_TRACKDIR_BIT; + } else if (v->current_order.IsType(OT_GOTO_DEPOT)) { + m_dest_station = INVALID_STATION; + m_dest_depot = v->current_order.GetDestination(); + m_destTile = CalcClosestDepotTile(m_dest_depot, v->tile); + m_destTrackdirs = INVALID_TRACKDIR_BIT; } else { m_dest_station = INVALID_STATION; + m_dest_depot = INVALID_DEPOT; m_destTile = v->dest_tile; m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_ROAD, GetRoadTramType(v->roadtype))); } @@ -287,6 +295,11 @@ public: (m_non_artic || IsDriveThroughStopTile(tile)); } + if (m_dest_depot != INVALID_DEPOT) { + return IsRoadDepotTile(tile) && + GetDepotIndex(tile) == m_dest_depot; + } + return tile == m_destTile && HasTrackdir(m_destTrackdirs, trackdir); } diff --git a/src/rail.h b/src/rail.h index 149996aae0..f15b9d6703 100644 --- a/src/rail.h +++ b/src/rail.h @@ -337,6 +337,19 @@ inline bool HasPowerOnRail(RailType enginetype, RailType tiletype) return HasBit(GetRailTypeInfo(enginetype)->powered_railtypes, tiletype); } +/** + * Checks if an engine with a given \a enginetype is powered on \a rail_types. + * This would normally just be an equality check, + * but for electric rails (which also support non-electric vehicles). + * @param enginetype The RailType of the engine we are considering. + * @param rail_types The RailTypes we are considering. + * @return Whether the engine got power on this tile. + */ +static inline bool HasPowerOnRails(RailType enginetype, RailTypes rail_types) +{ + return (GetRailTypeInfo(enginetype)->powered_railtypes & rail_types) != 0; +} + /** * Test if a RailType disallows build of level crossings. * @param rt The RailType to check. diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 0429661eda..b2172560b5 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); - 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,10 +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 */ - InvalidateWindowData(WC_VEHICLE_DEPOT, tile); - InvalidateWindowData(WC_BUILD_VEHICLE, tile); + 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; @@ -1754,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); @@ -1765,6 +1785,8 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) { + assert(IsRailDepotTile(tile)); + if (_current_company != OWNER_WATER) { CommandCost ret = CheckTileOwnership(tile); if (ret.Failed()) return ret; @@ -1774,9 +1796,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)) { @@ -1784,19 +1808,46 @@ 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]); } +/** + * Remove train depots from an area + * @param flags operation to perform + * @param start_tile start tile of the area + * @param end_tile end tile of the area + * @return the cost of this operation or an error + */ +CommandCost CmdRemoveTrainDepot(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile) +{ + assert(IsValidTile(start_tile)); + assert(IsValidTile(end_tile)); + + CommandCost cost(EXPENSES_CONSTRUCTION); + TileArea ta(start_tile, end_tile); + for (TileIndex t : ta) { + if (!IsRailDepotTile(t)) continue; + CommandCost ret = RemoveTrainDepot(t, flags); + if (ret.Failed()) return ret; + cost.AddCost(ret); + } + + return cost; +} + static CommandCost ClearTile_Track(TileIndex tile, DoCommandFlag flags) { CommandCost cost(EXPENSES_CONSTRUCTION); @@ -2774,7 +2825,7 @@ static bool ClickTile_Track(TileIndex tile) { if (!IsRailDepot(tile)) return false; - ShowDepotWindow(tile, VEH_TRAIN); + ShowDepotWindow(GetDepotIndex(tile)); return true; } @@ -2973,7 +3024,7 @@ static VehicleEnterTileStatus VehicleEnter_Track(Vehicle *u, TileIndex tile, int if (v->Next() == nullptr) VehicleEnterDepot(v->First()); v->tile = tile; - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); return VETSB_ENTERED_WORMHOLE; } diff --git a/src/rail_cmd.h b/src/rail_cmd.h index b9f6c0cc03..775bfa7ed7 100644 --- a/src/rail_cmd.h +++ b/src/rail_cmd.h @@ -19,7 +19,8 @@ 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 CmdRemoveTrainDepot(DoCommandFlag flags, TileIndex start_tile, 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); @@ -31,6 +32,7 @@ DEF_CMD_TRAIT(CMD_REMOVE_RAILROAD_TRACK, CmdRemoveRailroadTrack, CMD_AUTO, DEF_CMD_TRAIT(CMD_BUILD_SINGLE_RAIL, CmdBuildSingleRail, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_REMOVE_SINGLE_RAIL, CmdRemoveSingleRail, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_BUILD_TRAIN_DEPOT, CmdBuildTrainDepot, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_REMOVE_TRAIN_DEPOT, CmdRemoveTrainDepot, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_BUILD_SINGLE_SIGNAL, CmdBuildSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_REMOVE_SINGLE_SIGNAL, CmdRemoveSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_CONVERT_RAIL, CmdConvertRail, 0, CMDT_LANDSCAPE_CONSTRUCTION) @@ -40,6 +42,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 06819e4c3d..226691f8ec 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -41,6 +41,7 @@ #include "timer/timer.h" #include "timer/timer_game_calendar.h" #include "picker_gui.h" +#include "depot_func.h" #include "station_map.h" #include "tunnelbridge_map.h" @@ -116,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; @@ -137,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]); + } } } @@ -317,6 +320,7 @@ void CcBuildRailTunnel(Commands, const CommandCost &result, TileIndex tile) static void ToggleRailButton_Remove(Window *w) { CloseWindowById(WC_SELECT_STATION, 0); + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); w->ToggleWidgetLoweredState(WID_RAT_REMOVE); w->SetWidgetDirty(WID_RAT_REMOVE); _remove_button_clicked = w->IsWidgetLowered(WID_RAT_REMOVE); @@ -333,8 +337,9 @@ static bool RailToolbar_CtrlChanged(Window *w) if (w->IsWidgetDisabled(WID_RAT_REMOVE)) return false; /* allow ctrl to switch remove mode only for these widgets */ + for (WidgetID i = WID_RAT_BUILD_NS; i <= WID_RAT_BUILD_STATION; i++) { - if ((i <= WID_RAT_AUTORAIL || i >= WID_RAT_BUILD_WAYPOINT) && w->IsWidgetLowered(i)) { + if ((i <= WID_RAT_AUTORAIL || i >= WID_RAT_BUILD_DEPOT) && w->IsWidgetLowered(i)) { ToggleRailButton_Remove(w); return true; } @@ -454,6 +459,7 @@ struct BuildRailToolbarWindow : Window { if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) SetViewportCatchmentWaypoint(nullptr, true); if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false); CloseWindowById(WC_SELECT_STATION, 0); + if (this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); this->Window::Close(); } @@ -533,6 +539,7 @@ struct BuildRailToolbarWindow : Window { case WID_RAT_BUILD_EW: case WID_RAT_BUILD_Y: case WID_RAT_AUTORAIL: + case WID_RAT_BUILD_DEPOT: case WID_RAT_BUILD_WAYPOINT: case WID_RAT_BUILD_STATION: case WID_RAT_BUILD_SIGNALS: @@ -688,9 +695,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, _remove_button_clicked ? DDSP_REMOVE_DEPOT : DDSP_BUILD_DEPOT); + VpSetPlaceSizingLimit(_settings_game.depot.depot_spread); break; + } case WID_RAT_BUILD_WAYPOINT: PlaceRail_Waypoint(tile); @@ -786,6 +798,20 @@ struct BuildRailToolbarWindow : Window { } } break; + + case DDSP_BUILD_DEPOT: + if (_remove_button_clicked) { + Command::Post(STR_ERROR_CAN_T_REMOVE_TRAIN_DEPOT, CcPlaySound_CONSTRUCTION_RAIL, start_tile, end_tile); + } else { + 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; } } } @@ -795,6 +821,8 @@ struct BuildRailToolbarWindow : Window { if (this->IsWidgetLowered(WID_RAT_BUILD_STATION)) SetViewportCatchmentStation(nullptr, true); if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) SetViewportCatchmentWaypoint(nullptr, true); + if (this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); + this->RaiseButtons(); this->DisableWidget(WID_RAT_REMOVE); this->SetWidgetDirty(WID_RAT_REMOVE); @@ -804,6 +832,7 @@ struct BuildRailToolbarWindow : Window { CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_RAIL); CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_RAIL); CloseWindowById(WC_SELECT_STATION, 0); + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); CloseWindowByClass(WC_BUILD_BRIDGE); } @@ -815,8 +844,11 @@ struct BuildRailToolbarWindow : Window { EventState OnCTRLStateChange() override { - /* do not toggle Remove button by Ctrl when placing station */ - if (!this->IsWidgetLowered(WID_RAT_BUILD_STATION) && !this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT) && RailToolbar_CtrlChanged(this)) return ES_HANDLED; + /* do not toggle Remove button by Ctrl when placing station or depot */ + if (!this->IsWidgetLowered(WID_RAT_BUILD_STATION) && + !this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT) && + !this->IsWidgetLowered(WID_RAT_BUILD_DEPOT) && + RailToolbar_CtrlChanged(this)) return ES_HANDLED; return ES_NOT_HANDLED; } @@ -1705,6 +1737,12 @@ struct BuildRailDepotWindow : public PickerWindowBase { this->LowerWidget(WID_BRAD_DEPOT_NE + _build_depot_direction); } + void Close([[maybe_unused]] int data = 0) override + { + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); + this->PickerWindowBase::Close(); + } + void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override { if (!IsInsideMM(widget, WID_BRAD_DEPOT_NE, WID_BRAD_DEPOT_NW + 1)) return; @@ -1734,6 +1772,7 @@ struct BuildRailDepotWindow : public PickerWindowBase { case WID_BRAD_DEPOT_SE: case WID_BRAD_DEPOT_SW: case WID_BRAD_DEPOT_NW: + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); this->RaiseWidget(WID_BRAD_DEPOT_NE + _build_depot_direction); _build_depot_direction = (DiagDirection)(widget - WID_BRAD_DEPOT_NE); this->LowerWidget(WID_BRAD_DEPOT_NE + _build_depot_direction); @@ -1742,6 +1781,11 @@ struct BuildRailDepotWindow : public PickerWindowBase { break; } } + + void OnRealtimeTick([[maybe_unused]] uint delta_ms) override + { + CheckRedrawDepotHighlight(this, VEH_TRAIN); + } }; /** Nested widget definition of the build rail depot window */ diff --git a/src/road.h b/src/road.h index 44f4fcd198..c2591d2f1d 100644 --- a/src/road.h +++ b/src/road.h @@ -244,6 +244,19 @@ inline bool HasPowerOnRoad(RoadType enginetype, RoadType tiletype) return HasBit(GetRoadTypeInfo(enginetype)->powered_roadtypes, tiletype); } +/** + * Checks if an engine with a given \a enginetype is powered on \a road_types. + * This would normally just be an equality check, + * but for electrified roads (which also support non-electric vehicles). + * @param enginetype The RoadType of the engine we are considering. + * @param rail_types The RoadTypes we are considering. + * @return Whether the engine got power on this tile. + */ +static inline bool HasPowerOnRoads(RoadType enginetype, RoadTypes road_types) +{ + return (GetRoadTypeInfo(enginetype)->powered_roadtypes & road_types) != 0; +} + /** * Returns the cost of building the specified roadtype. * @param roadtype The roadtype being built. diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index e4489c8621..3dd28cd934 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); - 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]); @@ -2081,7 +2095,7 @@ static bool ClickTile_Road(TileIndex tile) { if (!IsRoadDepot(tile)) return false; - ShowDepotWindow(tile, VEH_ROAD); + ShowDepotWindow(GetDepotIndex(tile)); return true; } @@ -2269,7 +2283,7 @@ static VehicleEnterTileStatus VehicleEnter_Road(Vehicle *v, TileIndex tile, int, if (rv->Next() == nullptr) VehicleEnterDepot(rv->First()); rv->tile = tile; - InvalidateWindowData(WC_VEHICLE_DEPOT, rv->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(rv->tile)); return VETSB_ENTERED_WORMHOLE; } break; @@ -2543,8 +2557,9 @@ CommandCost CmdConvertRoad(DoCommandFlag flags, TileIndex tile, TileIndex area_s if (IsRoadDepotTile(tile)) { /* Update build vehicle window related to this depot */ - InvalidateWindowData(WC_VEHICLE_DEPOT, tile); - InvalidateWindowData(WC_BUILD_VEHICLE, tile); + DepotID depot_id = GetDepotIndex(tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); + InvalidateWindowData(WC_BUILD_VEHICLE, depot_id); } } } else { 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 27066a2144..53a71d0d2c 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -43,6 +43,7 @@ #include "picker_gui.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "depot_func.h" #include "widgets/road_widget.h" @@ -170,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); + } } /** @@ -368,6 +373,7 @@ struct BuildRoadToolbarWindow : Window { { if (_game_mode == GM_NORMAL && (this->IsWidgetLowered(WID_ROT_BUS_STATION) || this->IsWidgetLowered(WID_ROT_TRUCK_STATION))) SetViewportCatchmentStation(nullptr, true); if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false); + if (_game_mode == GM_NORMAL && this->IsWidgetLowered(WID_ROT_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); this->Window::Close(); } @@ -636,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: @@ -673,6 +681,8 @@ struct BuildRoadToolbarWindow : Window { { if (_game_mode != GM_EDITOR && (this->IsWidgetLowered(WID_ROT_BUS_STATION) || this->IsWidgetLowered(WID_ROT_TRUCK_STATION))) SetViewportCatchmentStation(nullptr, true); + if (_game_mode == GM_NORMAL && this->IsWidgetLowered(WID_ROT_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); + this->RaiseButtons(); this->SetWidgetDisabledState(WID_ROT_REMOVE, true); this->SetWidgetDirty(WID_ROT_REMOVE); @@ -687,6 +697,7 @@ struct BuildRoadToolbarWindow : Window { CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_ROAD); CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_ROAD); CloseWindowById(WC_SELECT_STATION, 0); + CloseWindowById(WC_SELECT_DEPOT, VEH_ROAD); CloseWindowByClass(WC_BUILD_BRIDGE); } @@ -805,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; @@ -1106,6 +1129,12 @@ struct BuildRoadDepotWindow : public PickerWindowBase { this->FinishInitNested(TRANSPORT_ROAD); } + void Close([[maybe_unused]] int data = 0) override + { + CloseWindowById(WC_SELECT_DEPOT, VEH_ROAD); + this->PickerWindowBase::Close(); + } + void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override { if (!IsInsideMM(widget, WID_BROD_DEPOT_NE, WID_BROD_DEPOT_NW + 1)) return; @@ -1135,6 +1164,7 @@ struct BuildRoadDepotWindow : public PickerWindowBase { case WID_BROD_DEPOT_NE: case WID_BROD_DEPOT_SW: case WID_BROD_DEPOT_SE: + CloseWindowById(WC_SELECT_DEPOT, VEH_ROAD); this->RaiseWidget(WID_BROD_DEPOT_NE + _road_depot_orientation); _road_depot_orientation = (DiagDirection)(widget - WID_BROD_DEPOT_NE); this->LowerWidget(WID_BROD_DEPOT_NE + _road_depot_orientation); @@ -1146,6 +1176,11 @@ struct BuildRoadDepotWindow : public PickerWindowBase { break; } } + + void OnRealtimeTick([[maybe_unused]] uint delta_ms) override + { + CheckRedrawDepotHighlight(this, VEH_ROAD); + } }; static constexpr NWidgetPart _nested_build_road_depot_widgets[] = { diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 957dda01ca..96adeb23ab 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; @@ -1018,7 +1056,9 @@ bool RoadVehLeaveDepot(RoadVehicle *v, bool first) if (first) { /* We are leaving a depot, but have to go to the exact same one; re-enter */ - if (v->current_order.IsType(OT_GOTO_DEPOT) && v->tile == v->dest_tile) { + if (v->current_order.IsType(OT_GOTO_DEPOT) && + IsRoadDepotTile(v->tile) && + v->current_order.GetDestination() == GetDepotIndex(v->tile)) { VehicleEnterDepot(v); return true; } @@ -1043,7 +1083,7 @@ bool RoadVehLeaveDepot(RoadVehicle *v, bool first) v->UpdatePosition(); v->UpdateInclination(true, true); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); return true; } diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 06102930b7..d621a05962 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -294,6 +294,10 @@ static void InitializeWindowsAndCaches() } } + for (Depot *dep : Depot::Iterate()) { + dep->RescanDepotTiles(); + } + RecomputePrices(); GroupStatistics::UpdateAfterLoad(); @@ -795,6 +799,16 @@ bool AfterLoadGame() _settings_game.linkgraph.recalc_time *= CalendarTime::SECONDS_PER_DAY; } + if (IsSavegameVersionBefore(SLV_DEPOT_SPREAD)) { + _settings_game.depot.depot_spread = DEF_MAX_DEPOT_SPREAD; + _settings_game.depot.distant_join_depots = true; + } + + if (IsSavegameVersionBefore(SLV_ALLOW_INCOMPATIBLE_REPLACEMENTS)) { + _settings_game.depot.allow_no_comp_railtype_replacements = false; + _settings_game.depot.allow_no_comp_roadtype_replacements = false; + } + /* Load the sprites */ GfxLoadSprites(); LoadStringWidthTable(); @@ -2425,28 +2439,6 @@ bool AfterLoadGame() for (Depot *d : Depot::Iterate()) d->build_date = TimerGameCalendar::date; } - /* In old versions it was possible to remove an airport while a plane was - * taking off or landing. This gives all kind of problems when building - * another airport in the same station so we don't allow that anymore. - * For old savegames with such aircraft we just throw them in the air and - * treat the aircraft like they were flying already. */ - if (IsSavegameVersionBefore(SLV_146)) { - for (Aircraft *v : Aircraft::Iterate()) { - if (!v->IsNormalAircraft()) continue; - Station *st = GetTargetAirportIfValid(v); - if (st == nullptr && v->state != FLYING) { - v->state = FLYING; - UpdateAircraftCache(v); - AircraftNextAirportPos_and_Order(v); - /* get aircraft back on running altitude */ - if ((v->vehstatus & VS_CRASHED) == 0) { - GetAircraftFlightLevelBounds(v, &v->z_pos, nullptr); - SetAircraftPosition(v, v->x_pos, v->y_pos, GetAircraftFlightLevel(v)); - } - } - } - } - /* Move the animation frame to the same location (m7) for all objects. */ if (IsSavegameVersionBefore(SLV_147)) { for (auto t : Map::Iterate()) { @@ -2792,6 +2784,102 @@ bool AfterLoadGame() } } + if (IsSavegameVersionBefore(SLV_ADD_DEPOTS_TO_HANGARS)) { + for (Station *st : Station::Iterate()) { + if ((st->facilities & FACIL_AIRPORT) && st->airport.HasHangar()) { + /* Add a built-in hangar for some airport types. */ + assert(Depot::CanAllocateItem()); + st->airport.AddHangar(); + } else { + /* If airport has no hangar, remove old go to hangar orders + * that could remain from removing an airport with a hangar + * and rebuilding it with an airport with no hangar. */ + RemoveOrderFromAllVehicles(OT_GOTO_DEPOT, st->index); + } + } + } + + if (IsSavegameVersionBefore(SLV_DEPOTID_IN_HANGAR_ORDERS)) { + /* Update go to hangar orders so they store the DepotID instead of StationID. */ + for (Aircraft *a : Aircraft::Iterate()) { + if (!a->IsNormalAircraft()) continue; + + /* Update current order. */ + if (a->current_order.IsType(OT_GOTO_DEPOT)) { + Depot *dep = Station::Get(a->current_order.GetDestination())->airport.hangar; + if (dep == nullptr) { + /* Aircraft heading to a removed hangar. */ + a->current_order.MakeDummy(); + } else { + a->current_order.SetDestination(dep->index); + } + } + + /* Update each aircraft order list once. */ + if (a->orders == nullptr) continue; + if (a->orders->GetFirstSharedVehicle() != a) continue; + + for (Order *order : a->Orders()) { + if (!order->IsType(OT_GOTO_DEPOT)) continue; + StationID station_id = order->GetDestination(); + Station *st = Station::Get(station_id); + order->SetDestination(st->airport.hangar->index); + } + } + } + + if (IsSavegameVersionBefore(SLV_ADD_MEMBERS_TO_DEPOT_STRUCT)) { + for (Depot *depot : Depot::Iterate()) { + if (!IsDepotTile(depot->xy) || GetDepotIndex(depot->xy) != depot->index) { + /* It can happen there is no depot here anymore (TTO/TTD savegames) */ + depot->veh_type = VEH_INVALID; + depot->owner = INVALID_OWNER; + delete depot; + continue; + } + + depot->owner = GetTileOwner(depot->xy); + depot->veh_type = GetDepotVehicleType(depot->xy); + switch (depot->veh_type) { + case VEH_SHIP: + depot->AfterAddRemove(TileArea(depot->xy, 2, 2), true); + break; + case VEH_ROAD: + case VEH_TRAIN: + depot->AfterAddRemove(TileArea(depot->xy, 1, 1), true); + break; + case VEH_AIRCRAFT: + assert(IsHangarTile(depot->xy)); + depot->station = Station::GetByTile(depot->xy); + break; + default: + break; + } + } + } + + /* In old versions it was possible to remove an airport while a plane was + * taking off or landing. This gives all kind of problems when building + * another airport in the same station so we don't allow that anymore. + * For old savegames with such aircraft we just throw them in the air and + * treat the aircraft like they were flying already. */ + if (IsSavegameVersionBefore(SLV_146)) { + for (Aircraft *v : Aircraft::Iterate()) { + if (!v->IsNormalAircraft()) continue; + Station *st = GetTargetAirportIfValid(v); + if (st == nullptr && v->state != FLYING) { + v->state = FLYING; + UpdateAircraftCache(v); + AircraftNextAirportPos_and_Order(v); + /* get aircraft back on running altitude */ + if ((v->vehstatus & VS_CRASHED) == 0) { + GetAircraftFlightLevelBounds(v, &v->z_pos, nullptr); + SetAircraftPosition(v, v->x_pos, v->y_pos, GetAircraftFlightLevel(v)); + } + } + } + } + /* This triggers only when old snow_lines were copied into the snow_line_height. */ if (IsSavegameVersionBefore(SLV_164) && _settings_game.game_creation.snow_line_height >= MIN_SNOWLINE_HEIGHT * TILE_HEIGHT) { _settings_game.game_creation.snow_line_height /= TILE_HEIGHT; diff --git a/src/saveload/compat/station_sl_compat.h b/src/saveload/compat/station_sl_compat.h index 1c24a8d5d9..e7b1dbab68 100644 --- a/src/saveload/compat/station_sl_compat.h +++ b/src/saveload/compat/station_sl_compat.h @@ -108,6 +108,7 @@ const SaveLoadCompat _station_normal_sl_compat[] = { SLC_VAR("airport.layout"), SLC_VAR("airport.flags"), SLC_VAR("airport.rotation"), + SLC_VAR("airport.hangar"), SLC_VAR("storage"), SLC_VAR("airport.psa"), SLC_VAR("indtype"), diff --git a/src/saveload/depot_sl.cpp b/src/saveload/depot_sl.cpp index 2a7859211c..1987605ea3 100644 --- a/src/saveload/depot_sl.cpp +++ b/src/saveload/depot_sl.cpp @@ -27,6 +27,12 @@ static const SaveLoad _depot_desc[] = { SLE_CONDVAR(Depot, town_cn, SLE_UINT16, SLV_141, SL_MAX_VERSION), SLE_CONDSSTR(Depot, name, SLE_STR, SLV_141, SL_MAX_VERSION), SLE_CONDVAR(Depot, build_date, SLE_INT32, SLV_142, SL_MAX_VERSION), + SLE_CONDVAR(Depot, owner, SLE_UINT8, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDVAR(Depot, veh_type, SLE_UINT8, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDVAR(Depot, ta.tile, SLE_UINT32, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDVAR(Depot, ta.w, SLE_FILE_U8 | SLE_VAR_U16, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDVAR(Depot, ta.h, SLE_FILE_U8 | SLE_VAR_U16, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDREF(Depot, station, REF_STATION, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), }; struct DEPTChunkHandler : ChunkHandler { diff --git a/src/saveload/oldloader_sl.cpp b/src/saveload/oldloader_sl.cpp index 538c1336c3..dc48d09399 100644 --- a/src/saveload/oldloader_sl.cpp +++ b/src/saveload/oldloader_sl.cpp @@ -690,6 +690,8 @@ static bool LoadOldDepot(LoadgameState *ls, int num) if (d->xy != 0) { d->town = RemapTown(d->xy); } else { + d->owner = INVALID_OWNER; + d->veh_type = VEH_INVALID; delete d; } diff --git a/src/saveload/order_sl.cpp b/src/saveload/order_sl.cpp index 0ce1bd206e..fe7e6d22d3 100644 --- a/src/saveload/order_sl.cpp +++ b/src/saveload/order_sl.cpp @@ -16,6 +16,7 @@ #include "../order_backup.h" #include "../settings_type.h" #include "../network/network.h" +#include "../depot_map.h" #include "../safeguards.h" @@ -243,11 +244,14 @@ struct ORDLChunkHandler : ChunkHandler { } }; +static TileIndex _tile; + SaveLoadTable GetOrderBackupDescription() { static const SaveLoad _order_backup_desc[] = { SLE_VAR(OrderBackup, user, SLE_UINT32), - SLE_VAR(OrderBackup, tile, SLE_UINT32), + SLEG_CONDVAR("tile", _tile, SLE_UINT32, SL_MIN_VERSION, SLV_DEPOTID_BACKUP_ORDERS), + SLE_CONDVAR(OrderBackup, depot_id, SLE_UINT16, SLV_DEPOTID_BACKUP_ORDERS, SL_MAX_VERSION), SLE_VAR(OrderBackup, group, SLE_UINT16), SLE_CONDVAR(OrderBackup, service_interval, SLE_FILE_U32 | SLE_VAR_U16, SL_MIN_VERSION, SLV_192), SLE_CONDVAR(OrderBackup, service_interval, SLE_UINT16, SLV_192, SL_MAX_VERSION), @@ -297,6 +301,8 @@ struct BKORChunkHandler : ChunkHandler { OrderBackup *ob = new (index) OrderBackup(); SlObject(ob, slt); } + + if (IsSavegameVersionBefore(SLV_DEPOTID_BACKUP_ORDERS)) _order_backup_pool.CleanPool(); } void FixPointers() const override diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 24eb4aa54d..e1962e98c7 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -23,6 +23,7 @@ #include "../stdafx.h" #include "../debug.h" #include "../station_base.h" +#include "../depot_base.h" #include "../thread.h" #include "../town.h" #include "../network/network.h" @@ -1121,6 +1122,7 @@ static size_t ReferenceToInt(const void *obj, SLRefType rt) case REF_STORAGE: return ((const PersistentStorage*)obj)->index + 1; case REF_LINK_GRAPH: return ((const LinkGraph*)obj)->index + 1; case REF_LINK_GRAPH_JOB: return ((const LinkGraphJob*)obj)->index + 1; + case REF_DEPOT: return ((const Depot*)obj)->index + 1; default: NOT_REACHED(); } } @@ -1202,6 +1204,10 @@ static void *IntToReference(size_t index, SLRefType rt) if (LinkGraphJob::IsValidID(index)) return LinkGraphJob::Get(index); SlErrorCorrupt("Referencing invalid LinkGraphJob"); + case REF_DEPOT: + if (Depot::IsValidID(index)) return Depot::Get(index); + SlErrorCorrupt("Referencing invalid Depot"); + default: NOT_REACHED(); } } diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 9149d42711..d67f9a5bef 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -383,6 +383,13 @@ enum SaveLoadVersion : uint16_t { SLV_GROUP_NUMBERS, ///< 336 PR#12297 Add per-company group numbers. SLV_INCREASE_STATION_TYPE_FIELD_SIZE, ///< 337 PR#12572 Increase size of StationType field in map array SLV_ROAD_WAYPOINTS, ///< 338 PR#12572 Road waypoints + SLV_ADD_DEPOTS_TO_HANGARS, ///< 339 PR#10691 Add depots to airports that have a hangar. + + SLV_DEPOTID_IN_HANGAR_ORDERS, ///< 340 PR#10691 Go to hangar orders store the DepotID instead of StationID. + SLV_DEPOTID_BACKUP_ORDERS, ///< 341 PR#XXXXX Backup orders are indexed through DepotIDs. + SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, ///< 342 PR#XXXXX Add some members to depot struct. + SLV_DEPOT_SPREAD, ///< 343 PR#XXXXX Add a setting for max depot spread. + SLV_ALLOW_INCOMPATIBLE_REPLACEMENTS, ///< 344 PR#XXXXX Allow incompatible vehicle replacements. SL_MAX_VERSION, ///< Highest possible saveload version }; @@ -598,6 +605,7 @@ enum SLRefType { REF_STORAGE = 9, ///< Load/save a reference to a persistent storage. REF_LINK_GRAPH = 10, ///< Load/save a reference to a link graph. REF_LINK_GRAPH_JOB = 11, ///< Load/save a reference to a link graph job. + REF_DEPOT = 12, ///< Load/save a reference to a depot. }; /** diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 2f427d8d52..6b84cdb56f 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -610,6 +610,7 @@ public: SLE_CONDVAR(Station, airport.layout, SLE_UINT8, SLV_145, SL_MAX_VERSION), SLE_VAR(Station, airport.flags, SLE_UINT64), SLE_CONDVAR(Station, airport.rotation, SLE_UINT8, SLV_145, SL_MAX_VERSION), + SLE_CONDREF(Station, airport.hangar, REF_DEPOT, SLV_ADD_DEPOTS_TO_HANGARS, SL_MAX_VERSION), SLEG_CONDARR("storage", _old_st_persistent_storage.storage, SLE_UINT32, 16, SLV_145, SLV_161), SLE_CONDREF(Station, airport.psa, REF_STORAGE, SLV_161, SL_MAX_VERSION), diff --git a/src/script/api/script_depotlist.cpp b/src/script/api/script_depotlist.cpp index eb43139165..7997d399e7 100644 --- a/src/script/api/script_depotlist.cpp +++ b/src/script/api/script_depotlist.cpp @@ -17,33 +17,16 @@ ScriptDepotList::ScriptDepotList(ScriptTile::TransportType transport_type) { EnforceDeityOrCompanyModeValid_Void(); - ::TileType tile_type; - switch (transport_type) { - default: return; + static_assert(VEH_TRAIN == (int)ScriptTile::TRANSPORT_RAIL); + static_assert(VEH_ROAD == (int)ScriptTile::TRANSPORT_ROAD); + static_assert(VEH_SHIP == (int)ScriptTile::TRANSPORT_WATER); - case ScriptTile::TRANSPORT_ROAD: tile_type = ::MP_ROAD; break; - case ScriptTile::TRANSPORT_RAIL: tile_type = ::MP_RAILWAY; break; - case ScriptTile::TRANSPORT_WATER: tile_type = ::MP_WATER; break; - - case ScriptTile::TRANSPORT_AIR: { - /* Hangars are not seen as real depots by the depot code. */ - bool is_deity = ScriptCompanyMode::IsDeity(); - CompanyID owner = ScriptObject::GetCompany(); - for (const Station *st : Station::Iterate()) { - if (is_deity || st->owner == owner) { - for (uint i = 0; i < st->airport.GetNumHangars(); i++) { - this->AddItem(st->airport.GetHangarTile(i).base()); - } - } - } - return; - } - } - - /* Handle 'standard' depots. */ bool is_deity = ScriptCompanyMode::IsDeity(); CompanyID owner = ScriptObject::GetCompany(); for (const Depot *depot : Depot::Iterate()) { - if ((is_deity || ::GetTileOwner(depot->xy) == owner) && ::IsTileType(depot->xy, tile_type)) this->AddItem(depot->xy.base()); + if (depot->veh_type != (VehicleType)transport_type || + (!is_deity && ::GetTileOwner(depot->xy) != owner)) continue; + + this->AddItem(depot->xy.base()); } } 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_order.cpp b/src/script/api/script_order.cpp index b68f7fd67a..5dcd39ac4e 100644 --- a/src/script/api/script_order.cpp +++ b/src/script/api/script_order.cpp @@ -245,18 +245,13 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr const Order *order = ::ResolveOrder(vehicle_id, order_position); if (order == nullptr || order->GetType() == OT_CONDITIONAL) return INVALID_TILE; - const Vehicle *v = ::Vehicle::Get(vehicle_id); switch (order->GetType()) { case OT_GOTO_DEPOT: { /* We don't know where the nearest depot is... (yet) */ if (order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) return INVALID_TILE; - if (v->type != VEH_AIRCRAFT) return ::Depot::Get(order->GetDestination())->xy; - /* Aircraft's hangars are referenced by StationID, not DepotID */ - const Station *st = ::Station::Get(order->GetDestination()); - if (!st->airport.HasHangar()) return INVALID_TILE; - return st->airport.GetHangarTile(0); + return ::Depot::Get(order->GetDestination())->xy; } case OT_GOTO_STATION: { diff --git a/src/script/api/script_rail.cpp b/src/script/api/script_rail.cpp index ac1b9fc5a9..f0c79fcff9 100644 --- a/src/script/api/script_rail.cpp +++ b/src/script/api/script_rail.cpp @@ -145,7 +145,16 @@ 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::RemoveRailDepot(TileIndex start_tile, TileIndex end_tile) +{ + EnforceCompanyModeValid(false); + EnforcePrecondition(false, ::IsValidTile(start_tile)); + EnforcePrecondition(false, ::IsValidTile(end_tile)); + + return ScriptObject::Command::Do(start_tile, end_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_rail.hpp b/src/script/api/script_rail.hpp index 2a55a72048..71e5e8032f 100644 --- a/src/script/api/script_rail.hpp +++ b/src/script/api/script_rail.hpp @@ -240,6 +240,18 @@ public: */ static bool BuildRailDepot(TileIndex tile, TileIndex front); + /** + * Removes rail depots from an area. + * @param start_tile Start tile of the area. + * @param end_tile End tile of the area. + * @pre ScriptMap::IsValidTile(start_tile). + * @pre ScriptMap::IsValidTile(end_tile). + * @game @pre Valid ScriptCompanyMode active in scope. + * @exception ScriptError::ERR_FLAT_LAND_REQUIRED + * @return Whether all depot tiles of the owner in the area have been/can be cleared or not. + */ + static bool RemoveRailDepot(TileIndex start_tile, TileIndex end_tile); + /** * Build a rail station. * @param tile Place to build the station. 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/settings_gui.cpp b/src/settings_gui.cpp index fb5c08ec86..739a339a7a 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -2145,6 +2145,15 @@ static SettingsContainer &GetSettingsTree() SettingsPage *limitations = main->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS)); { + SettingsPage *depots = limitations->Add(new SettingsPage(STR_CONFIG_SETTING_DEPOTS)); + { + depots->Add(new SettingEntry("depot.depot_spread")); + depots->Add(new SettingEntry("depot.distant_join_depots")); + + depots->Add(new SettingEntry("depot.allow_no_comp_railtype_replacements")); + depots->Add(new SettingEntry("depot.allow_no_comp_roadtype_replacements")); + } + limitations->Add(new SettingEntry("construction.command_pause_level")); limitations->Add(new SettingEntry("construction.autoslope")); limitations->Add(new SettingEntry("construction.extra_dynamite")); diff --git a/src/settings_table.cpp b/src/settings_table.cpp index 9e06fe4e12..8e97e6bc1e 100644 --- a/src/settings_table.cpp +++ b/src/settings_table.cpp @@ -380,6 +380,22 @@ static void SpriteZoomMinChanged(int32_t) MarkWholeScreenDirty(); } +static bool CheckDifferentRailRoadTypesReplacements(int32_t &new_value) +{ + if (_game_mode == GM_NORMAL) { + if (new_value == 0) { + ShowErrorMessage(STR_CONFIG_SETTING_REPLACEMENTS_DIFF_TYPE, INVALID_STRING_ID, WL_ERROR); + return false; + } + } + return true; +} + +static void InvalidateReplacementWindows(int32_t) +{ + InvalidateWindowClassesData(WC_REPLACE_VEHICLE); +} + /** * Update any possible saveload window and delete any newgrf dialogue as * its widget parts might change. Reinit all windows as it allows access to the diff --git a/src/settings_type.h b/src/settings_type.h index ae6f22c42e..4d1451e0ae 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -570,6 +570,15 @@ struct StationSettings { uint8_t station_spread; ///< amount a station may spread }; +/** Settings related to depots. */ +struct DepotSettings { + uint8_t depot_spread; ///< amount a depot may spread + bool distant_join_depots; ///< allow to join non-adjacent depots + + bool allow_no_comp_railtype_replacements; ///< allow replacing rail vehicles even if rail type is not compatible + bool allow_no_comp_roadtype_replacements; ///< allow replacing road vehicles even if road type is not compatible +}; + /** Default settings for vehicles. */ struct VehicleDefaultSettings { bool servint_ispercent; ///< service intervals are in percents @@ -603,6 +612,7 @@ struct GameSettings { EconomySettings economy; ///< settings to change the economy LinkGraphSettings linkgraph; ///< settings for link graph calculations StationSettings station; ///< settings related to station management + DepotSettings depot; ///< settings related to depot management LocaleSettings locale; ///< settings related to used currency/unit system in the current game }; diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index dcd2f6825f..498817772d 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -187,14 +187,14 @@ static const Depot *FindClosestShipDepot(const Vehicle *v, uint max_distance) const Depot *best_depot = nullptr; uint best_dist_sq = std::numeric_limits::max(); for (const Depot *depot : Depot::Iterate()) { + if (depot->veh_type != VEH_SHIP || depot->owner != v->owner) continue; + const TileIndex tile = depot->xy; - if (IsShipDepotTile(tile) && IsTileOwner(tile, v->owner)) { - const uint dist_sq = DistanceSquare(tile, v->tile); - if (dist_sq < best_dist_sq && dist_sq <= max_distance * max_distance && - visited_patch_hashes.count(CalculateWaterRegionPatchHash(GetWaterRegionPatchInfo(tile))) > 0) { - best_dist_sq = dist_sq; - best_depot = depot; - } + const uint dist_sq = DistanceSquare(tile, v->tile); + if (dist_sq < best_dist_sq && dist_sq <= max_distance * max_distance && + visited_patch_hashes.count(CalculateWaterRegionPatchHash(GetWaterRegionPatchInfo(tile))) > 0) { + best_dist_sq = dist_sq; + best_depot = depot; } } @@ -222,7 +222,7 @@ static void CheckIfShipNeedsService(Vehicle *v) } v->current_order.MakeGoToDepot(depot->index, ODTFB_SERVICE); - v->SetDestTile(depot->xy); + v->SetDestTile(depot->GetBestDepotTile(v)); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); } @@ -424,12 +424,13 @@ static bool CheckShipLeaveDepot(Ship *v) v->cur_speed = 0; v->UpdateViewport(true, true); - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + DepotID depot_id = GetDepotIndex(v->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, depot_id); VehicleServiceInDepot(v); v->LeaveUnbunchingDepot(); v->PlayLeaveStationSound(); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); SetWindowClassesDirty(WC_SHIPS_LIST); return false; @@ -961,5 +962,5 @@ ClosestDepot Ship::FindClosestDepot() const Depot *depot = FindClosestShipDepot(this, MAX_SHIP_DEPOT_SEARCH_DISTANCE); if (depot == nullptr) return ClosestDepot(); - return ClosestDepot(depot->xy, depot->index); + return ClosestDepot(depot->GetBestDepotTile(this), depot->index); } diff --git a/src/station.cpp b/src/station.cpp index e0dab59067..0d4198ac6c 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -26,6 +26,7 @@ #include "core/random_func.hpp" #include "linkgraph/linkgraph.h" #include "linkgraph/linkgraphschedule.h" +#include "depot_base.h" #include "table/strings.h" @@ -727,6 +728,52 @@ Money AirportMaintenanceCost(Owner owner) return total_cost >> 3; } +/** + * Create a hangar on the airport. + */ +void Airport::AddHangar() +{ + assert(this->hangar == nullptr); + assert(Depot::CanAllocateItem()); + assert(this->GetNumHangars() > 0); + Station *st = Station::GetByTile(this->GetHangarTile(0)); + this->hangar = new Depot(this->GetHangarTile(0), VEH_AIRCRAFT, st->owner, st); + this->hangar->build_date = st->build_date; + this->hangar->town = st->town; + + this->hangar->ta.tile = st->airport.tile; + this->hangar->ta.w = st->airport.w; + this->hangar->ta.h = st->airport.h; + + for (uint i = 0; i < this->GetNumHangars(); i++) { + this->hangar->depot_tiles.push_back(this->GetHangarTile(i)); + } +} + +/** + * Delete the hangar on the airport. + */ +void Airport::RemoveHangar() +{ + RemoveOrderFromAllVehicles(OT_GOTO_DEPOT, this->hangar->index); + + for (Aircraft *a : Aircraft::Iterate()) { + if (!a->IsNormalAircraft()) continue; + if (!a->current_order.IsType(OT_GOTO_DEPOT)) continue; + if (a->current_order.GetDestination() != this->hangar->index) continue; + a->current_order.MakeDummy(); + } + + delete this->hangar; + this->hangar = nullptr; +} + +DepotID GetHangarIndex(TileIndex t) { + assert(IsAirportTile(t)); + assert(Station::GetByTile(t)->airport.hangar != nullptr); + return Station::GetByTile(t)->airport.hangar->index; +} + bool StationCompare::operator() (const Station *lhs, const Station *rhs) const { return lhs->index < rhs->index; diff --git a/src/station_base.h b/src/station_base.h index 6dda3ca843..f5db44f23b 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -18,6 +18,7 @@ #include "linkgraph/linkgraph_type.h" #include "newgrf_storage.h" #include "bitmap_type.h" +#include "depot_type.h" static const uint8_t INITIAL_STATION_RATING = 175; static const uint8_t MAX_STATION_RATING = 255; @@ -294,6 +295,7 @@ struct Airport : public TileArea { uint8_t type; ///< Type of this airport, @see AirportTypes uint8_t layout; ///< Airport layout number. Direction rotation; ///< How this airport is rotated. + Depot *hangar; ///< The corresponding hangar of this airport, if any. PersistentStorage *psa; ///< Persistent storage for NewGRF airports. @@ -404,6 +406,9 @@ struct Airport : public TileArea { return num; } + void AddHangar(); + void RemoveHangar(); + private: /** * Retrieve hangar information of a hangar at a given tile. diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 0dd4edb779..a0e8f8c805 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -67,6 +67,7 @@ #include "timer/timer_game_tick.h" #include "cheat_type.h" #include "road_func.h" +#include "depot_base.h" #include "widgets/station_widget.h" @@ -2539,6 +2540,8 @@ CommandCost CmdBuildAirport(DoCommandFlag flags, TileIndex tile, uint8_t airport /* Check if a valid, buildable airport was chosen for construction */ const AirportSpec *as = AirportSpec::Get(airport_type); + + if (!as->depots.empty() && !Depot::CanAllocateItem()) return CMD_ERROR; if (!as->IsAvailable() || layout >= as->layouts.size()) return CMD_ERROR; if (!as->IsWithinMapBounds(layout, tile)) return CMD_ERROR; @@ -2632,6 +2635,8 @@ CommandCost CmdBuildAirport(DoCommandFlag flags, TileIndex tile, uint8_t airport AirportTileAnimationTrigger(st, iter, AAT_BUILT); } + if (!as->depots.empty()) st->airport.AddHangar(); + UpdateAirplanesOnNewStation(st); Company::Get(st->owner)->infrastructure.airport++; @@ -2674,11 +2679,7 @@ static CommandCost RemoveAirport(TileIndex tile, DoCommandFlag flags) } if (flags & DC_EXEC) { - for (uint i = 0; i < st->airport.GetNumHangars(); ++i) { - TileIndex tile_cur = st->airport.GetHangarTile(i); - OrderBackup::Reset(tile_cur, false); - CloseWindowById(WC_VEHICLE_DEPOT, tile_cur); - } + st->airport.RemoveHangar(); /* The noise level is the noise from the airport and reduce it to account for the distance to the town center. * And as for construction, always remove it, even if the setting is not set, in order to avoid the @@ -3692,8 +3693,8 @@ static bool ClickTile_Station(TileIndex tile) if (bst->facilities & FACIL_WAYPOINT) { ShowWaypointWindow(Waypoint::From(bst)); } else if (IsHangar(tile)) { - const Station *st = Station::From(bst); - ShowDepotWindow(st->airport.GetHangarTile(st->airport.GetHangarNum(tile)), VEH_AIRCRAFT); + assert(Station::From(bst)->airport.HasHangar()); + ShowDepotWindow(Station::From(bst)->airport.hangar->index); } else { ShowStationViewWindow(bst->index); } diff --git a/src/table/settings/game_settings.ini b/src/table/settings/game_settings.ini index 07adda5cc9..269da0c70b 100644 --- a/src/table/settings/game_settings.ini +++ b/src/table/settings/game_settings.ini @@ -21,6 +21,8 @@ static bool CheckRoadSide(int32_t &new_value); static bool CheckDynamicEngines(int32_t &new_value); static void StationCatchmentChanged(int32_t new_value); static void MaxVehiclesChanged(int32_t new_value); +static bool CheckDifferentRailRoadTypesReplacements(int32_t &new_value); +static void InvalidateReplacementWindows(int32_t new_value); static const SettingVariant _game_settings_table[] = { [post-amble] @@ -145,6 +147,47 @@ str = STR_CONFIG_SETTING_DISTANT_JOIN_STATIONS strhelp = STR_CONFIG_SETTING_DISTANT_JOIN_STATIONS_HELPTEXT post_cb = [](auto) { CloseWindowById(WC_SELECT_STATION, 0); } +[SDT_BOOL] +var = depot.distant_join_depots +from = SLV_DEPOT_SPREAD +def = true +str = STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS +strhelp = STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS_HELPTEXT +post_cb = [](auto) { CloseWindowByClass(WC_SELECT_DEPOT); } + +[SDT_VAR] +var = depot.depot_spread +type = SLE_UINT8 +from = SLV_DEPOT_SPREAD +def = DEF_MAX_DEPOT_SPREAD +min = 1 +max = 64 +str = STR_CONFIG_SETTING_DEPOT_SPREAD +strhelp = STR_CONFIG_SETTING_DEPOT_SPREAD_HELPTEXT +strval = STR_CONFIG_SETTING_TILE_LENGTH +post_cb = [](auto) { CloseWindowByClass(WC_SELECT_DEPOT); } +cat = SC_BASIC + +[SDT_BOOL] +var = depot.allow_no_comp_railtype_replacements +from = SLV_ALLOW_INCOMPATIBLE_REPLACEMENTS +def = false +str = STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_RAIL +strhelp = STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_RAIL_HELPTEXT +pre_cb = CheckDifferentRailRoadTypesReplacements +post_cb = InvalidateReplacementWindows +cat = SC_EXPERT + +[SDT_BOOL] +var = depot.allow_no_comp_roadtype_replacements +from = SLV_ALLOW_INCOMPATIBLE_REPLACEMENTS +def = false +str = STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_ROAD +strhelp = STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_ROAD_HELPTEXT +pre_cb = CheckDifferentRailRoadTypesReplacements +post_cb = InvalidateReplacementWindows +cat = SC_EXPERT + [SDT_OMANY] var = vehicle.road_side type = SLE_UINT8 diff --git a/src/tilehighlight_func.h b/src/tilehighlight_func.h index 572c5bd43e..770013bc59 100644 --- a/src/tilehighlight_func.h +++ b/src/tilehighlight_func.h @@ -26,6 +26,8 @@ void VpStartDragging(ViewportDragDropSelectionProcess process); void VpStartPlaceSizing(TileIndex tile, ViewportPlaceMethod method, ViewportDragDropSelectionProcess process); void VpSetPresizeRange(TileIndex from, TileIndex to); void VpSetPlaceSizingLimit(int limit); +void VpSetPlaceFixedSize(uint8_t fixed_size); +void VpResetFixedSize(); void UpdateTileSelection(); diff --git a/src/tilehighlight_type.h b/src/tilehighlight_type.h index a19eef5aac..a7a43d179e 100644 --- a/src/tilehighlight_type.h +++ b/src/tilehighlight_type.h @@ -55,11 +55,12 @@ struct TileHighlightData { Point new_pos; ///< New value for \a pos; used to determine whether to redraw the selection. Point new_size; ///< New value for \a size; used to determine whether to redraw the selection. Point new_outersize; ///< New value for \a outersize; used to determine whether to redraw the selection. - uint8_t dirty; ///< Whether the build station window needs to redraw due to the changed selection. + uint8_t dirty; ///< Whether the build station window needs to redraw due to the changed selection. Point selstart; ///< The location where the dragging started. Point selend; ///< The location where the drag currently ends. - uint8_t sizelimit; ///< Whether the selection is limited in length, and what the maximum length is. + uint8_t sizelimit; ///< Whether the selection is limited in length, and what the maximum length is. + uint8_t fixed_size; ///< The fixed length for one of the sides. HighLightStyle drawstyle; ///< Lower bits 0-3 are reserved for detailed highlight information. HighLightStyle next_drawstyle; ///< Queued, but not yet drawn style. diff --git a/src/train.h b/src/train.h index bbf1e04365..be4307307c 100644 --- a/src/train.h +++ b/src/train.h @@ -353,4 +353,6 @@ protected: // These functions should not be called outside acceleration code. } }; +bool HasCompatibleDepotTile(TileIndex tile, const Train *t); + #endif /* TRAIN_H */ diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 2b58b6b212..dc0ca252ef 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -7,6 +7,7 @@ /** @file train_cmd.cpp Handling of trains. */ +#include "depot_map.h" #include "stdafx.h" #include "error.h" #include "articulated_vehicles.h" @@ -38,6 +39,7 @@ #include "misc_cmd.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" +#include "depot_base.h" #include "table/strings.h" #include "table/train_sprites.h" @@ -604,6 +606,65 @@ void GetTrainSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, } } + +/** + * Check if a train chain is compatible with a depot tile. + * @param tile Tile to check. + * @param t Train chain to check. + * @return Whether the full train chain is compatible with this tile. + */ +bool IsVehicleCompatibleWithDepotTile(TileIndex tile, const Train *t) +{ + assert(IsRailDepotTile(tile)); + for (const Train *u = t; u != nullptr; u = u->Next()) { + RailType rail_type = Engine::Get(u->engine_type)->u.rail.railtype; + if (!IsCompatibleRail(rail_type, GetRailType(tile))) return false; + } + + return true; +} + +/** + * Check if a depot has a tile where a train chain can be stored. + * @param tile A tile of the depot. + * @param t The train to check. + * @return True iff the depot has a tile compatible with the chain. + */ +bool HasCompatibleDepotTile(TileIndex tile, const Train *t) +{ + assert(IsRailDepotTile(tile)); + Depot *dep = Depot::GetByTile(tile); + + for (auto &depot_tile : dep->depot_tiles) { + if (IsVehicleCompatibleWithDepotTile(depot_tile, t)) return true; + } + + return false; +} + +/** + * Find a tile of a depot compatible with the rail type of a rail vehicle. + * @param depot_id Index of the depot. + * @param rail_type Rail type of the new vehicle. + * @param is_engine Whether the vehicle is an engine. + * @return A compatible tile of the depot or INVALID_TILE if no compatible tile is found. + */ +TileIndex FindCompatibleDepotTile(DepotID depot_id, RailType rail_type, bool is_engine) +{ + assert(Depot::IsValidID(depot_id)); + Depot *depot = Depot::Get(depot_id); + + for (auto &dep_tile : depot->depot_tiles) { + if (is_engine) { + if (HasPowerOnRail(rail_type, GetRailType(dep_tile))) return dep_tile; + } else { + if (IsCompatibleRail(rail_type, GetRailType(dep_tile))) return dep_tile; + } + } + + return INVALID_TILE; +} + /** * Build a railroad wagon. * @param flags type of operation. @@ -615,9 +676,12 @@ void GetTrainSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, static CommandCost CmdBuildRailWagon(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **ret) { const RailVehicleInfo *rvi = &e->u.rail; + assert(IsRailDepotTile(tile)); + DepotID depot_id = GetDepotIndex(tile); - /* Check that the wagon can drive on the track in question */ - if (!IsCompatibleRail(rvi->railtype, GetRailType(tile))) return CMD_ERROR; + /* Find a good tile to place the wagon. */ + tile = FindCompatibleDepotTile(depot_id, rvi->railtype, false); + if (tile == INVALID_TILE) return CMD_ERROR; if (flags & DC_EXEC) { Train *v = new Train(); @@ -645,7 +709,7 @@ static CommandCost CmdBuildRailWagon(DoCommandFlag flags, TileIndex tile, const v->SetWagon(); v->SetFreeWagon(); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); v->cargo_type = e->GetDefaultCargoType(); assert(IsValidCargoID(v->cargo_type)); @@ -673,7 +737,8 @@ static CommandCost CmdBuildRailWagon(DoCommandFlag flags, TileIndex tile, const /* Try to connect the vehicle to one of free chains of wagons. */ for (Train *w : Train::Iterate()) { - if (w->tile == tile && ///< Same depot + if (!IsRailDepotTile(w->tile)) continue; + if (GetDepotIndex(w->tile) == depot_id && ///< Same depot w->IsFreeWagon() && ///< A free wagon chain w->engine_type == e->index && ///< Same type w->First() != v && ///< Don't connect to ourself @@ -692,9 +757,10 @@ static CommandCost CmdBuildRailWagon(DoCommandFlag flags, TileIndex tile, const void NormalizeTrainVehInDepot(const Train *u) { assert(u->IsEngine()); + DepotID dep_id = GetDepotIndex(u->tile); for (const Train *v : Train::Iterate()) { - if (v->IsFreeWagon() && v->tile == u->tile && - v->track == TRACK_BIT_DEPOT) { + if (v->IsFreeWagon() && v->IsInDepot() && + GetDepotIndex(v->tile) == dep_id) { if (Command::Do(DC_EXEC, v->index, u->index, true).Failed()) { break; } @@ -748,13 +814,14 @@ static void AddRearEngineToMultiheadedTrain(Train *v) */ CommandCost CmdBuildRailVehicle(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **ret) { + assert(IsRailDepotTile(tile)); const RailVehicleInfo *rvi = &e->u.rail; if (rvi->railveh_type == RAILVEH_WAGON) return CmdBuildRailWagon(flags, tile, e, ret); - /* Check if depot and new engine uses the same kind of tracks * - * We need to see if the engine got power on the tile to avoid electric engines in non-electric depots */ - if (!HasPowerOnRail(rvi->railtype, GetRailType(tile))) return CMD_ERROR; + /* Find a good tile to place the engine and get power on it. */ + tile = FindCompatibleDepotTile(GetDepotIndex(tile), rvi->railtype, true); + if (tile == INVALID_TILE) return CMD_ERROR; if (flags & DC_EXEC) { DiagDirection dir = GetRailDepotDirection(tile); @@ -824,10 +891,10 @@ CommandCost CmdBuildRailVehicle(DoCommandFlag flags, TileIndex tile, const Engin static Train *FindGoodVehiclePos(const Train *src) { EngineID eng = src->engine_type; - TileIndex tile = src->tile; + DepotID dep_id = GetDepotIndex(src->tile); for (Train *dst : Train::Iterate()) { - if (dst->IsFreeWagon() && dst->tile == tile && !(dst->vehstatus & VS_CRASHED)) { + if (dst->IsFreeWagon() && !(dst->vehstatus & VS_CRASHED) && GetDepotIndex(dst->tile) == dep_id) { /* check so all vehicles in the line have the same engine. */ Train *t = dst; while (t->engine_type == eng) { @@ -984,6 +1051,82 @@ static CommandCost CheckNewTrain(Train *original_dst, Train *dst, Train *origina return_cmd_error(STR_ERROR_TOO_MANY_VEHICLES_IN_GAME); } +/** + * Check if a train can be placed in a depot tile. + * @param train The train. + * @param tile The tile to check whether it is possible to place the train. + * @return whether it found a depot tile in which to place the train. + */ +bool CheckPlacement(const Train *train, TileIndex tile) +{ + assert(train != nullptr); + assert(IsRailDepotTile(tile)); + + RailType rt = GetRailType(tile); + for (const Train *t = train; t != nullptr; t = t->Next()) { + RailType rail_type = Engine::Get(t->engine_type)->u.rail.railtype; + if (!IsCompatibleRail(rail_type, rt)) return false; + } + + return true; +} + +/** + * Find a valid tile before placing a train in the depot. + * @param t The train to place in a rail depot tile. + * @return a compatible tile, if any, preferabily the one the first vehicle is or INVALID_TILE if none found. + */ +TileIndex LookForTileInDepot(const Train *train) +{ + assert(train != nullptr); + assert(IsRailDepotTile(train->tile)); + TileIndex best_tile = INVALID_TILE; + + /* First candidate is the original position of the train. */ + if (CheckPlacement(train, train->tile)) { + if (HasPowerOnRail(train->railtype, GetRailType(train->tile))) return train->tile; + best_tile = train->tile; + } + + /* Check all depot tiles. */ + Depot *depot = Depot::GetByTile(train->tile); + for (std::vector::iterator it = depot->depot_tiles.begin(); it != depot->depot_tiles.end(); ++it) { + if (CheckPlacement(train, *it)) { + if (HasPowerOnRail(train->railtype, GetRailType(*it))) return *it; + if (best_tile == INVALID_TILE) best_tile = *it; + } + } + + return best_tile; +} + +/** + * Find an appropriate depot tile for a train and place + * all the vehicle chain in the same depot tile. + * @param train The train to place. + */ +void PlaceOnRailDepot(Train *train) +{ + assert(train->First() == train); + + TileIndex depot_tile = LookForTileInDepot(train); + assert(depot_tile != INVALID_TILE); + + DiagDirection diag_dir = GetRailDepotDirection(depot_tile); + int x = TileX(depot_tile) * TILE_SIZE + _vehicle_initial_x_fract[diag_dir]; + int y = TileY(depot_tile) * TILE_SIZE + _vehicle_initial_y_fract[diag_dir]; + for (Train *t = train; t != nullptr; t = t->Next()) { + t->tile = depot_tile; + t->direction = DiagDirToDir(diag_dir); + t->vehstatus |= VS_HIDDEN; + t->track = TRACK_BIT_DEPOT; + t->x_pos = x; + t->y_pos = y; + t->z_pos = GetSlopePixelZ(x, y); + t->UpdatePosition(); + } +} + /** * Check whether the train parts can be attached. * @param t the train to check @@ -994,6 +1137,8 @@ static CommandCost CheckTrainAttachment(Train *t) /* No multi-part train, no need to check. */ if (t == nullptr || t->Next() == nullptr) return CommandCost(); + if (LookForTileInDepot(t) == INVALID_TILE) return_cmd_error(STR_ERROR_INCOMPATIBLE_RAILTYPES_WITH_DEPOT); + /* The maximum length for a train. For each part we decrease this by one * and if the result is negative the train is simply too long. */ int allowed_len = _settings_game.vehicle.max_train_length * TILE_SIZE - t->gcache.cached_veh_length; @@ -1229,7 +1374,7 @@ CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID Train *dst_head; if (dst != nullptr) { dst_head = dst->First(); - if (dst_head->tile != src_head->tile) return CMD_ERROR; + if (GetDepotIndex(dst_head->tile) != GetDepotIndex(src_head->tile)) return CMD_ERROR; /* Now deal with articulated part of destination wagon */ dst = dst->GetLastEnginePart(); } else { @@ -1359,11 +1504,18 @@ CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID CheckCargoCapacity(dst_head); } - if (src_head != nullptr) src_head->First()->MarkDirty(); - if (dst_head != nullptr) dst_head->First()->MarkDirty(); + if (src_head != nullptr) { + PlaceOnRailDepot(src_head->First()); + src_head->First()->MarkDirty(); + } + + if (dst_head != nullptr) { + PlaceOnRailDepot(dst_head->First()); + dst_head->First()->MarkDirty(); + } /* We are undoubtedly changing something in the depot and train list. */ - InvalidateWindowData(WC_VEHICLE_DEPOT, src->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(src->tile)); InvalidateWindowClassesData(WC_TRAINS_LIST, 0); } else { /* We don't want to execute what we're just tried. */ @@ -1447,7 +1599,7 @@ CommandCost CmdSellRailWagon(DoCommandFlag flags, Vehicle *t, bool sell_chain, b NormaliseTrainHead(new_head); /* We are undoubtedly changing something in the depot and train list. */ - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); InvalidateWindowClassesData(WC_TRAINS_LIST, 0); /* Actually delete the sold 'goods' */ @@ -1966,7 +2118,7 @@ void ReverseTrainDirection(Train *v) { if (IsRailDepotTile(v->tile)) { if (IsWholeTrainInsideDepot(v)) return; - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); } /* Clear path reservation in front if train is not stuck. */ @@ -1989,7 +2141,7 @@ void ReverseTrainDirection(Train *v) AdvanceWagonsAfterSwap(v); if (IsRailDepotTile(v->tile)) { - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); } ToggleBit(v->flags, VRF_TOGGLE_REVERSE); @@ -2079,7 +2231,7 @@ CommandCost CmdReverseTrainDirection(DoCommandFlag flags, VehicleID veh_id, bool ToggleBit(v->flags, VRF_REVERSE_DIRECTION); front->ConsistChanged(CCF_ARRANGE); - SetWindowDirty(WC_VEHICLE_DEPOT, front->tile); + if (IsRailDepotTile(front->tile)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(front->tile)); SetWindowDirty(WC_VEHICLE_DETAILS, front->index); SetWindowDirty(WC_VEHICLE_VIEW, front->index); SetWindowClassesDirty(WC_TRAINS_LIST); @@ -2273,7 +2425,7 @@ static bool CheckTrainStayInDepot(Train *v) /* if the train got no power, then keep it in the depot */ if (v->gcache.cached_power == 0) { v->vehstatus |= VS_STOPPED; - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); return true; } @@ -2302,7 +2454,9 @@ static bool CheckTrainStayInDepot(Train *v) } /* We are leaving a depot, but have to go to the exact same one; re-enter. */ - if (v->current_order.IsType(OT_GOTO_DEPOT) && v->tile == v->dest_tile) { + if (v->current_order.IsType(OT_GOTO_DEPOT) && + IsRailDepotTile(v->tile) && + v->current_order.GetDestination() == GetDepotIndex(v->tile)) { /* Service when depot has no reservation. */ if (!HasDepotReservation(v->tile)) VehicleEnterDepot(v); return true; @@ -2334,7 +2488,7 @@ static bool CheckTrainStayInDepot(Train *v) v->UpdatePosition(); UpdateSignalsOnSegment(v->tile, INVALID_DIAGDIR, v->owner); v->UpdateAcceleration(); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); return false; } @@ -3654,7 +3808,7 @@ static void DeleteLastWagon(Train *v) /* Update the depot window if the first vehicle is in depot - * if v == first, then it is updated in PreDestructor() */ if (first->track == TRACK_BIT_DEPOT) { - SetWindowDirty(WC_VEHICLE_DEPOT, first->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(first->tile)); } v->last_station_visited = first->last_station_visited; // for PreDestructor } 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/vehicle.cpp b/src/vehicle.cpp index 9db7da2fa1..d10f8dcb0f 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -297,7 +297,7 @@ uint Vehicle::Crash(bool) InvalidateWindowClassesData(GetWindowClassForVehicleType(this->type), 0); SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP); SetWindowDirty(WC_VEHICLE_DETAILS, this->index); - SetWindowDirty(WC_VEHICLE_DEPOT, this->tile); + if (IsDepotTile(this->tile)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(this->tile)); delete this->cargo_payment; assert(this->cargo_payment == nullptr); // cleared by ~CargoPayment @@ -868,8 +868,8 @@ void Vehicle::PreDestructor() if (v->disaster_vehicle != INVALID_VEHICLE) ReleaseDisasterVehicle(v->disaster_vehicle); } - if (this->Previous() == nullptr) { - InvalidateWindowData(WC_VEHICLE_DEPOT, this->tile); + if (this->Previous() == nullptr && IsDepotTile(this->tile)) { + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(this->tile)); } if (this->IsPrimaryVehicle()) { @@ -1554,6 +1554,7 @@ void VehicleEnterDepot(Vehicle *v) /* Always work with the front of the vehicle */ assert(v == v->First()); + DepotID depot_id = GetDepotIndex(v->tile); switch (v->type) { case VEH_TRAIN: { Train *t = Train::From(v); @@ -1580,7 +1581,7 @@ void VehicleEnterDepot(Vehicle *v) ship->state = TRACK_BIT_DEPOT; ship->UpdateCache(); ship->UpdateViewport(true, true); - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, depot_id); break; } @@ -1595,9 +1596,9 @@ void VehicleEnterDepot(Vehicle *v) if (v->type != VEH_TRAIN) { /* Trains update the vehicle list when the first unit enters the depot and calls VehicleEnterDepot() when the last unit enters. * We only increase the number of vehicles when the first one enters, so we will not need to search for more vehicles in the depot */ - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); } - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, depot_id); v->vehstatus |= VS_HIDDEN; v->cur_speed = 0; @@ -1619,7 +1620,7 @@ void VehicleEnterDepot(Vehicle *v) * Note: The target depot for nearest-/manual-depot-orders is only updated on junctions, but we want to accept every depot. */ if ((v->current_order.GetDepotOrderType() & ODTFB_PART_OF_ORDERS) && real_order != nullptr && !(real_order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) && - (v->type == VEH_AIRCRAFT ? v->current_order.GetDestination() != GetStationIndex(v->tile) : v->dest_tile != v->tile)) { + v->current_order.GetDestination() != GetDepotIndex(v->tile)) { /* We are heading for another depot, keep driving. */ return; } diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index 6d22d56b0a..16ec099551 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -37,6 +37,7 @@ #include "roadveh_cmd.h" #include "train_cmd.h" #include "ship_cmd.h" +#include "depot_base.h" #include #include @@ -178,7 +179,7 @@ std::tuple CmdBuildVehicle(D NormalizeTrainVehInDepot(Train::From(v)); } - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); InvalidateWindowClassesData(GetWindowClassForVehicleType(type), 0); SetWindowDirty(WC_COMPANY, _current_company); if (IsLocalCompany()) { @@ -553,7 +554,7 @@ std::tuple CmdRefitVehicle(DoCommandFla InvalidateWindowData(WC_VEHICLE_DETAILS, front->index); InvalidateWindowClassesData(GetWindowClassForVehicleType(v->type), 0); } - SetWindowDirty(WC_VEHICLE_DEPOT, front->tile); + if (IsDepotTile(front->tile)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(front->tile)); } else { /* Always invalidate the cache; querycost might have filled it. */ v->InvalidateNewGRFCacheOfChain(); @@ -639,7 +640,7 @@ CommandCost CmdStartStopVehicle(DoCommandFlag flags, VehicleID veh_id, bool eval v->MarkDirty(); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + if (IsDepotTile(v->tile)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); SetWindowClassesDirty(GetWindowClassForVehicleType(v->type)); InvalidateWindowData(WC_VEHICLE_VIEW, v->index); } @@ -667,7 +668,7 @@ CommandCost CmdMassStartStopVehicle(DoCommandFlag flags, TileIndex tile, bool do } else { if (!IsDepotTile(tile) || !IsTileOwner(tile, _current_company)) return CMD_ERROR; /* Get the list of vehicles in the depot */ - BuildDepotVehicleList(vli.vtype, tile, &list, nullptr); + BuildDepotVehicleList(vli.vtype, GetDepotIndex(tile), &list, nullptr); } for (const Vehicle *v : list) { @@ -699,7 +700,7 @@ CommandCost CmdDepotSellAllVehicles(DoCommandFlag flags, TileIndex tile, Vehicle if (!IsDepotTile(tile) || !IsTileOwner(tile, _current_company)) return CMD_ERROR; /* Get the list of vehicles in the depot */ - BuildDepotVehicleList(vehicle_type, tile, &list, &list); + BuildDepotVehicleList(vehicle_type, GetDepotIndex(tile), &list, &list); CommandCost last_error = CMD_ERROR; bool had_success = false; @@ -732,7 +733,7 @@ CommandCost CmdDepotMassAutoReplace(DoCommandFlag flags, TileIndex tile, Vehicle if (!IsDepotTile(tile) || !IsTileOwner(tile, _current_company)) return CMD_ERROR; /* Get the list of vehicles in the depot */ - BuildDepotVehicleList(vehicle_type, tile, &list, &list, true); + BuildDepotVehicleList(vehicle_type, GetDepotIndex(tile), &list, &list, true); for (const Vehicle *v : list) { /* Ensure that the vehicle completely in the depot */ diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 2a43b351a3..bb277bab34 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -45,6 +45,7 @@ #include "train_cmd.h" #include "hotkeys.h" #include "group_cmd.h" +#include "depot_base.h" #include "safeguards.h" @@ -2122,7 +2123,7 @@ public: } case WID_VL_AVAILABLE_VEHICLES: - ShowBuildVehicleWindow(INVALID_TILE, this->vli.vtype); + ShowBuildVehicleWindow(INVALID_DEPOT, this->vli.vtype); break; case WID_VL_MANAGE_VEHICLES_DROPDOWN: { @@ -2267,16 +2268,9 @@ void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, StationI ShowVehicleListWindowLocal(company, VL_STATION_LIST, vehicle_type, station); } -void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, TileIndex depot_tile) +void ShowVehicleListWindowDepot(CompanyID company, VehicleType vehicle_type, DepotID depot_id) { - uint16_t depot_airport_index; - - if (vehicle_type == VEH_AIRCRAFT) { - depot_airport_index = GetStationIndex(depot_tile); - } else { - depot_airport_index = GetDepotIndex(depot_tile); - } - ShowVehicleListWindowLocal(company, VL_DEPOT_LIST, vehicle_type, depot_airport_index); + ShowVehicleListWindowLocal(company, VL_DEPOT_LIST, vehicle_type, depot_id); } @@ -3141,7 +3135,7 @@ public: case OT_GOTO_DEPOT: { SetDParam(0, v->type); - SetDParam(1, v->current_order.GetDestination()); + SetDParam(1, GetTargetDestination(v->current_order, v->type == VEH_AIRCRAFT)); SetDParam(2, PackVelocity(v->GetDisplaySpeed(), v->type)); if (v->current_order.GetDestination() == INVALID_DEPOT) { /* This case *only* happens when multiple nearest depot orders diff --git a/src/vehicle_gui.h b/src/vehicle_gui.h index ebd0d3fde8..cf5d8a381d 100644 --- a/src/vehicle_gui.h +++ b/src/vehicle_gui.h @@ -18,6 +18,7 @@ #include "station_type.h" #include "engine_type.h" #include "company_type.h" +#include "depot_type.h" void ShowVehicleRefitWindow(const Vehicle *v, VehicleOrderID order, Window *parent, bool auto_refit = false); @@ -56,7 +57,7 @@ void DrawRoadVehImage(const Vehicle *v, const Rect &r, VehicleID selection, Engi void DrawShipImage(const Vehicle *v, const Rect &r, VehicleID selection, EngineImageType image_type); void DrawAircraftImage(const Vehicle *v, const Rect &r, VehicleID selection, EngineImageType image_type); -void ShowBuildVehicleWindow(TileIndex tile, VehicleType type); +void ShowBuildVehicleWindow(DepotID depot_id, VehicleType type); uint ShowRefitOptionsList(int left, int right, int y, EngineID engine); StringID GetCargoSubtypeText(const Vehicle *v); @@ -64,7 +65,7 @@ StringID GetCargoSubtypeText(const Vehicle *v); void ShowVehicleListWindow(const Vehicle *v); void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type); void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, StationID station); -void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, TileIndex depot_tile); +void ShowVehicleListWindowDepot(CompanyID company, VehicleType vehicle_type, DepotID depot_id); /** * Get the height of a single vehicle in the GUIs. diff --git a/src/vehiclelist.cpp b/src/vehiclelist.cpp index 48214d9384..01bbc757df 100644 --- a/src/vehiclelist.cpp +++ b/src/vehiclelist.cpp @@ -13,6 +13,7 @@ #include "vehiclelist.h" #include "vehiclelist_func.h" #include "group.h" +#include "depot_base.h" #include "safeguards.h" @@ -95,19 +96,24 @@ static Vehicle *BuildDepotVehicleListProc(Vehicle *v, void *data) /** * Generate a list of vehicles inside a depot. - * @param type Type of vehicle - * @param tile The tile the depot is located on - * @param engines Pointer to list to add vehicles to - * @param wagons Pointer to list to add wagons to (can be nullptr) + * @param type Type of vehicle + * @param depot_id The id of the depot + * @param engines Pointer to list to add vehicles to + * @param wagons Pointer to list to add wagons to (can be nullptr) * @param individual_wagons If true add every wagon to \a wagons which is not attached to an engine. If false only add the first wagon of every row. */ -void BuildDepotVehicleList(VehicleType type, TileIndex tile, VehicleList *engines, VehicleList *wagons, bool individual_wagons) +void BuildDepotVehicleList(VehicleType type, DepotID depot_id, VehicleList *engines, VehicleList *wagons, bool individual_wagons) { + assert(Depot::IsValidID(depot_id)); + Depot *dep = Depot::Get(depot_id); engines->clear(); if (wagons != nullptr && wagons != engines) wagons->clear(); BuildDepotVehicleListData bdvld{engines, wagons, type, individual_wagons}; - FindVehicleOnPos(tile, &bdvld, BuildDepotVehicleListProc); + + for (TileIndex tile : dep->depot_tiles) { + FindVehicleOnPos(tile, &bdvld, BuildDepotVehicleListProc); + } } /** diff --git a/src/vehiclelist.h b/src/vehiclelist.h index 037953f25d..9bb3aa6dcf 100644 --- a/src/vehiclelist.h +++ b/src/vehiclelist.h @@ -12,6 +12,7 @@ #include "vehicle_type.h" #include "company_type.h" +#include "depot_type.h" #include "tile_type.h" /** Vehicle List type flags */ @@ -54,7 +55,7 @@ struct VehicleListIdentifier { typedef std::vector VehicleList; bool GenerateVehicleSortList(VehicleList *list, const VehicleListIdentifier &identifier); -void BuildDepotVehicleList(VehicleType type, TileIndex tile, VehicleList *engine_list, VehicleList *wagon_list, bool individual_wagons = false); +void BuildDepotVehicleList(VehicleType type, DepotID depot_id, VehicleList *engine_list, VehicleList *wagon_list, bool individual_wagons = false); uint GetUnitNumberDigits(VehicleList &vehicles); #endif /* VEHICLELIST_H */ diff --git a/src/viewport.cpp b/src/viewport.cpp index 1aa848058e..6a9da5e44d 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -90,6 +90,7 @@ #include "network/network_func.h" #include "framerate_type.h" #include "viewport_cmd.h" +#include "depot_map.h" #include #include @@ -1002,6 +1003,7 @@ enum TileHighlightType { const Station *_viewport_highlight_station; ///< Currently selected station for coverage area highlight const Waypoint *_viewport_highlight_waypoint; ///< Currently selected waypoint for coverage area highlight const Town *_viewport_highlight_town; ///< Currently selected town for coverage area highlight +DepotID _viewport_highlight_depot = INVALID_DEPOT; ///< Currently selected depot for depot highlight /** * Get tile highlight type of coverage area for a given tile. @@ -1010,6 +1012,10 @@ const Town *_viewport_highlight_town; ///< Currently selected town for coverage */ static TileHighlightType GetTileHighlightType(TileIndex t) { + if (_viewport_highlight_depot != INVALID_DEPOT) { + if (IsDepotTile(t) && GetDepotIndex(t) == _viewport_highlight_depot) return THT_BLUE; + } + if (_viewport_highlight_station != nullptr) { if (IsTileType(t, MP_STATION) && GetStationIndex(t) == _viewport_highlight_station->index) return THT_WHITE; if (_viewport_highlight_station->TileIsInCatchment(t)) return THT_BLUE; @@ -2674,6 +2680,8 @@ void UpdateTileSelection() } _thd.new_pos.x = x1 & ~TILE_UNIT_MASK; _thd.new_pos.y = y1 & ~TILE_UNIT_MASK; + if (_thd.select_method == VPM_LIMITED_X_FIXED_Y) _thd.new_size.y = (TILE_SIZE * _thd.fixed_size) & ~TILE_UNIT_MASK; + if (_thd.select_method == VPM_LIMITED_Y_FIXED_X) _thd.new_size.x = (TILE_SIZE * _thd.fixed_size) & ~TILE_UNIT_MASK; } } @@ -2766,6 +2774,15 @@ void VpSetPlaceSizingLimit(int limit) _thd.sizelimit = limit; } +void VpSetPlaceFixedSize(uint8_t fixed) +{ + _thd.fixed_size = fixed; +} + +void VpResetFixedSize() { + VpSetPlaceFixedSize(1); +} + /** * Highlights all tiles between a set of two tiles. Used in dock and tunnel placement * @param from TileIndex of the first tile to highlight @@ -3237,7 +3254,7 @@ void VpSelectTilesWithMethod(int x, int y, ViewportPlaceMethod method) sx = _thd.selstart.x; sy = _thd.selstart.y; - int limit = 0; + int limit = -1; switch (method) { case VPM_X_OR_Y: // drag in X or Y direction @@ -3250,28 +3267,33 @@ void VpSelectTilesWithMethod(int x, int y, ViewportPlaceMethod method) } goto calc_heightdiff_single_direction; + case VPM_LIMITED_Y_FIXED_X: case VPM_X_LIMITED: // Drag in X direction (limited size). limit = (_thd.sizelimit - 1) * TILE_SIZE; [[fallthrough]]; case VPM_FIX_X: // drag in Y direction - x = sx; + x = sx + (method == VPM_LIMITED_Y_FIXED_X ? (TILE_SIZE * (_thd.fixed_size - 1)) : 0) ; style = HT_DIR_Y; goto calc_heightdiff_single_direction; + case VPM_LIMITED_X_FIXED_Y: case VPM_Y_LIMITED: // Drag in Y direction (limited size). limit = (_thd.sizelimit - 1) * TILE_SIZE; [[fallthrough]]; case VPM_FIX_Y: // drag in X direction - y = sy; + y = sy + (method == VPM_LIMITED_X_FIXED_Y ? (TILE_SIZE * (_thd.fixed_size - 1)) : 0) ; style = HT_DIR_X; calc_heightdiff_single_direction:; - if (limit > 0) { - x = sx + Clamp(x - sx, -limit, limit); - y = sy + Clamp(y - sy, -limit, limit); + if (limit >= 0) { + if (method != VPM_LIMITED_X_FIXED_Y) y = sy + Clamp(y - sy, -limit, limit); + if (method != VPM_LIMITED_Y_FIXED_X) x = sx + Clamp(x - sx, -limit, limit); } + + if (method == VPM_LIMITED_Y_FIXED_X || method == VPM_LIMITED_X_FIXED_Y) goto measure_area; + if (_settings_client.gui.measure_tooltip) { TileIndex t0 = TileVirtXY(sx, sy); TileIndex t1 = TileVirtXY(x, y); @@ -3301,6 +3323,7 @@ calc_heightdiff_single_direction:; [[fallthrough]]; case VPM_X_AND_Y: // drag an X by Y area +measure_area: if (_settings_client.gui.measure_tooltip) { static const StringID measure_strings_area[] = { STR_NULL, STR_NULL, STR_MEASURE_AREA, STR_MEASURE_AREA_HEIGHTDIFF @@ -3589,6 +3612,9 @@ void MarkCatchmentTilesDirty() } MarkWholeScreenDirty(); } + if (_viewport_highlight_depot != INVALID_DEPOT) { + MarkWholeScreenDirty(); + } } static void SetWindowDirtyForViewportCatchment() @@ -3596,6 +3622,7 @@ static void SetWindowDirtyForViewportCatchment() if (_viewport_highlight_station != nullptr) SetWindowDirty(WC_STATION_VIEW, _viewport_highlight_station->index); if (_viewport_highlight_waypoint != nullptr) SetWindowDirty(WC_WAYPOINT_VIEW, _viewport_highlight_waypoint->index); if (_viewport_highlight_town != nullptr) SetWindowDirty(WC_TOWN_VIEW, _viewport_highlight_town->index); + if (_viewport_highlight_depot != INVALID_DEPOT) SetWindowDirty(WC_VEHICLE_DEPOT, _viewport_highlight_depot); } static void ClearViewportCatchment() @@ -3604,6 +3631,7 @@ static void ClearViewportCatchment() _viewport_highlight_station = nullptr; _viewport_highlight_waypoint = nullptr; _viewport_highlight_town = nullptr; + _viewport_highlight_depot = INVALID_DEPOT; } /** @@ -3665,3 +3693,30 @@ void SetViewportCatchmentTown(const Town *t, bool sel) } if (_viewport_highlight_town != nullptr) SetWindowDirty(WC_TOWN_VIEW, _viewport_highlight_town->index); } + +static void MarkDepotTilesDirty() +{ + if (_viewport_highlight_depot != INVALID_DEPOT) { + MarkWholeScreenDirty(); + return; + } +} + +/** + * Select or deselect depot to highlight. + * @param *dep Depot in question + * @param sel Select or deselect given depot + */ +void SetViewportHighlightDepot(const DepotID dep, bool sel) +{ + SetWindowDirtyForViewportCatchment(); + if (sel && _viewport_highlight_depot != dep) { + ClearViewportCatchment(); + _viewport_highlight_depot = dep; + MarkDepotTilesDirty(); + } else if (!sel && _viewport_highlight_depot == dep) { + MarkDepotTilesDirty(); + _viewport_highlight_depot = INVALID_DEPOT; + } + if (_viewport_highlight_depot != INVALID_DEPOT) SetWindowDirty(WC_VEHICLE_DEPOT, _viewport_highlight_depot); +} diff --git a/src/viewport_func.h b/src/viewport_func.h index 5b1537478c..94e132f88a 100644 --- a/src/viewport_func.h +++ b/src/viewport_func.h @@ -16,6 +16,7 @@ #include "tile_map.h" #include "station_type.h" #include "vehicle_type.h" +#include "depot_type.h" static const int TILE_HEIGHT_STEP = 50; ///< One Z unit tile height difference is displayed as 50m. @@ -102,6 +103,8 @@ struct Town; void SetViewportCatchmentStation(const Station *st, bool sel); void SetViewportCatchmentWaypoint(const Waypoint *wp, bool sel); void SetViewportCatchmentTown(const Town *t, bool sel); +void SetViewportHighlightDepot(const DepotID dep, bool sel); + void MarkCatchmentTilesDirty(); template diff --git a/src/viewport_type.h b/src/viewport_type.h index 0fde051f38..e5ef362fa9 100644 --- a/src/viewport_type.h +++ b/src/viewport_type.h @@ -99,6 +99,8 @@ enum ViewportPlaceMethod { VPM_FIX_VERTICAL = 6, ///< drag only in vertical direction VPM_X_LIMITED = 7, ///< Drag only in X axis with limited size VPM_Y_LIMITED = 8, ///< Drag only in Y axis with limited size + VPM_LIMITED_Y_FIXED_X = 9, ///< Drag only in Y axis with limited size and a fixed value for X axis + VPM_LIMITED_X_FIXED_Y = 10, ///< Drag only in X axis with limited size and a fixed value for Y axis VPM_RAILDIRS = 0x40, ///< all rail directions VPM_SIGNALDIRS = 0x80, ///< similar to VMP_RAILDIRS, but with different cursor }; @@ -120,6 +122,8 @@ enum ViewportDragDropSelectionProcess { DDSP_PLANT_TREES, ///< Plant trees DDSP_BUILD_BRIDGE, ///< Bridge placement DDSP_BUILD_OBJECT, ///< Build an object + DDSP_BUILD_DEPOT, ///< Depot placement + DDSP_REMOVE_DEPOT, ///< Depot removal /* Rail specific actions */ DDSP_PLACE_RAIL, ///< Rail placement diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index c4b01c05ed..c24cf6bd24 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); - 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]); @@ -1334,7 +1344,7 @@ static TrackStatus GetTileTrackStatus_Water(TileIndex tile, TransportType mode, static bool ClickTile_Water(TileIndex tile) { if (GetWaterTileType(tile) == WATER_TILE_DEPOT) { - ShowDepotWindow(GetShipDepotNorthTile(tile), VEH_SHIP); + ShowDepotWindow(GetDepotIndex(tile)); return true; } return false; 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); diff --git a/src/widgets/depot_widget.h b/src/widgets/depot_widget.h index f94f1263d2..8797fb36ff 100644 --- a/src/widgets/depot_widget.h +++ b/src/widgets/depot_widget.h @@ -27,9 +27,17 @@ enum DepotWidgets : WidgetID { WID_D_LOCATION, ///< Location button. WID_D_SHOW_RENAME, ///< Show rename panel. WID_D_RENAME, ///< Rename button. + WID_D_HIGHLIGHT, ///< Highlight button. WID_D_VEHICLE_LIST, ///< List of vehicles. WID_D_STOP_ALL, ///< Stop all button. WID_D_START_ALL, ///< Start all button. }; +/** Widgets of the #SelectDepotWindow class. */ +enum JoinDepotWidgets { + WID_JD_CAPTION, // Caption of the window. + WID_JD_PANEL, // Main panel. + WID_JD_SCROLLBAR, // Scrollbar of the panel. +}; + #endif /* WIDGETS_DEPOT_WIDGET_H */ diff --git a/src/window_type.h b/src/window_type.h index 0896d5ff6f..5ec2d3c332 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -241,6 +241,12 @@ enum WindowClass { */ WC_SELECT_STATION, + /** + * Select depot (when joining depots); %Window numbers: + * - #Vehicle type = #JoinDepotWidgets + */ + WC_SELECT_DEPOT, + /** * News window; %Window numbers: * - 0 = #NewsWidgets @@ -346,7 +352,7 @@ enum WindowClass { /** * Depot view; %Window numbers: - * - #TileIndex = #DepotWidgets + * - #DepotID = #DepotWidgets */ WC_VEHICLE_DEPOT, @@ -383,8 +389,8 @@ enum WindowClass { /** * Build vehicle; %Window numbers: - * - #VehicleType = #BuildVehicleWidgets - * - #TileIndex = #BuildVehicleWidgets + * - #DepotID = #BuildVehicleWidgets, for existing depots + * - #MAX_DEPOTS + VehicleType = #BuildVehicleWidgets, for "Available Trains"... */ WC_BUILD_VEHICLE,