diff --git a/docs/landscape.html b/docs/landscape.html index c48aedcebc..331678f809 100644 --- a/docs/landscape.html +++ b/docs/landscape.html @@ -520,7 +520,7 @@
  • m2 bit 11: opposite track is reserved, too
  • -
  • m5 bit 7 set, bit 6 set: railway depot +
  • m5 bit 7 set, bit 6 clear: railway depot
  • @@ -666,7 +671,7 @@ @@ -1047,55 +1061,55 @@ - 10..1B  + 40..4B  canal locks - + - + - + - + - + - + - + - + - + - + - + - +
    10  40  middle part, (SW-NE direction)
    11  41  middle part, (NW-SE direction)
    12  42  middle part, (NE-SW direction)
    13  43  middle part, (SE-NW direction)
    14  44  lower part, (SW-NE direction)
    15  45  lower part, (NW-SE direction)
    16  46  lower part, (NE-SW direction)
    17  47  lower part, (SE-NW direction)
    18  48  upper part, (SW-NE direction)
    19  49  upper part, (NW-SE direction)
    1A  4A  upper part, (NE-SW direction)
    1B  4B  upper part, (SE-NW direction)
    @@ -1127,6 +1141,11 @@ +
  • m5 bit 5 clear: standard depot (for depots only)
  • +
  • m5 bit 5 set: extended depot + diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html index 603d78025b..fb85f30152 100644 --- a/docs/landscape_grid.html +++ b/docs/landscape_grid.html @@ -79,8 +79,8 @@ the array so you can quickly see what is used and what is not. 0 ground - XXXX XX XX - XXXX XXXX + XXXX XX XX + XXXX XXXX OOO1 OOOO OOOO OOOO OOOO OOOO XXX XOOOO @@ -95,16 +95,16 @@ the array so you can quickly see what is used and what is not. XXX XXXXX - 1 + 1 rail - XOOX XXXX + XOOX XXXX OOOO XXXX OOOO OOOO OOOO OOOO OOOO XXXX OO XXXXXX - OOOO OOOO - OOOO OOOO - OOOO OOOO OOXX XXXX + OOOO OOOO + OOOO OOOO + OOOO OOOO OOXX XXXX rail with signals @@ -114,22 +114,27 @@ the array so you can quickly see what is used and what is not. O1 XXXXXX - depot - XXXX XXXX XXXX XXXX - OOOO OOOO + standard rail depot + XXXX XXXX XXXX XXXX + OOOO OOOO OOOO XXXX - 11OX OOXX + 1OOX OOXX - 2 + extended rail depot + XXOO XXXX + 1O1X OOXX + + + 2 road OOOX XXXX XXXX XXXX XXXX XXXX XXXX XXXX - OOXX XXXX + OO XXXXXX OO XX XXXX - OO XXXOOO - OOX OXXXX + OOXX XOOO + OOXO XXXX OOOO XXXX XXOO OOOO @@ -137,20 +142,27 @@ the array so you can quickly see what is used and what is not. OOOX XXXX XXXX OOOO O1 X XOOOX - OO XXXOOO + OOXX XOOO OOX XXXXX OOOO XXXX XX XXXXXX - road depot - OOOX XXXX - XXXX XXXX XXXX XXXX - XXXX OOOO - 1OOO OOXX - OOOO OOOO - OOX XXXXX - OOOO XXXX XXOO OOOO + standard road depot + OOOX XXXX + XXXX XXXX XXXX XXXX + XXXX XXXX + 1OOO XXXX + XXOO OOOO + OOX XXXXX + OOOO XXXX XXOO OOOO + + extended road depot + XX XXXXXX + 1O1O XXXX + XX XXOOOO + + 3 finished house @@ -240,33 +252,38 @@ the array so you can quickly see what is used and what is not. OOOO OOOO - 6 + 6 sea, shore - X XX XXXXX + X XX XXXXX OOOO OOOO OOOO OOOO - OOOO OOOO + OOOO OOOO OOOO OOOO - OOOO OOOX - OOOO OOOO - OOOO OOOO - OOOO OOOO OOOO OOOO + OOOO OOOX + OOOO OOOO + OOOO OOOO + OOOO OOOO OOOO OOOO canal, river XXXX XXXX - OOOO OOOO + OOOO OOOO lock OOOO OOOO - OOO1 XX XX + O1OOXX XX - shipdepot - XXXX XXXX XXXX XXXX + standard ship depot + XXXX XXXX XXXX XXXX OOOO OOOO - 1OOO OOX X + 1OOOOOX X + + extended ship depot + XXOO OOOO + 1O1OOOX X + 8 finished industry diff --git a/regression/regression/result.txt b/regression/regression/result.txt index 27a81675a9..7aaf66b68a 100644 --- a/regression/regression/result.txt +++ b/regression/regression/result.txt @@ -7364,7 +7364,7 @@ ERROR: IsEnd() is invalid as Begin() is never called IsBuoyTile(): false IsLockTile(): false IsCanalTile(): false - GetBankBalance(): 1999979304 + GetBankBalance(): 1999979244 BuildWaterDepot(): true BuildDock(): true BuildBuoy(): true @@ -7377,7 +7377,7 @@ ERROR: IsEnd() is invalid as Begin() is never called IsBuoyTile(): true IsLockTile(): true IsCanalTile(): true - GetBankBalance(): 1999964680 + GetBankBalance(): 1999964620 --AIWaypointList(BUOY)-- Count(): 1 @@ -7396,7 +7396,7 @@ ERROR: IsEnd() is invalid as Begin() is never called IsBuoyTile(): false IsLockTile(): false IsCanalTile(): false - GetBankBalance(): 1999959285 + GetBankBalance(): 1999959225 BuildWaterDepot(): true BuildDock(): true BuildBuoy(): true diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5f7847ff8a..ad909c65f5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -342,6 +342,9 @@ add_files( picker_func.h picker_gui.cpp picker_gui.h + platform_func.h + platform_type.h + platform.cpp progress.cpp progress.h querystring_gui.h @@ -501,6 +504,8 @@ add_files( train.h train_cmd.cpp train_cmd.h + train_placement.cpp + train_placement.h train_gui.cpp transparency.h transparency_gui.cpp diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 33ab2eba38..1bac641367 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,7 @@ 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); + if (IsHangarTile(v->tile)) InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); SetWindowClassesDirty(WC_AIRCRAFT_LIST); } @@ -1539,7 +1541,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 +1550,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 +1601,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 +2105,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 +2171,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..d94249e188 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -20,6 +20,7 @@ #include "core/random_func.hpp" #include "vehiclelist.h" #include "road.h" +#include "ship.h" #include "ai/ai.hpp" #include "news_func.h" #include "strings_func.h" @@ -28,6 +29,9 @@ #include "order_cmd.h" #include "train_cmd.h" #include "vehicle_cmd.h" +#include "depot_map.h" +#include "train_placement.h" +#include "news_func.h" #include "table/strings.h" @@ -71,7 +75,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 +86,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: @@ -364,13 +375,13 @@ static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehic if (refit_cargo != CARGO_NO_REFIT) { uint8_t subtype = GetBestFittingSubType(old_veh, new_veh, refit_cargo); - cost.AddCost(std::get<0>(Command::Do(DC_EXEC, new_veh->index, refit_cargo, subtype, false, false, 0))); + cost.AddCost(std::get<0>(Command::Do(DC_EXEC | DC_AUTOREPLACE, new_veh->index, refit_cargo, subtype, false, false, 0))); assert(cost.Succeeded()); // This should be ensured by GetNewCargoTypeForReplace() } /* Try to reverse the vehicle, but do not care if it fails as the new type might not be reversible */ if (new_veh->type == VEH_TRAIN && HasBit(Train::From(old_veh)->flags, VRF_REVERSE_DIRECTION)) { - Command::Do(DC_EXEC, new_veh->index, true); + Command::Do(DC_EXEC | DC_AUTOREPLACE, new_veh->index, true); } return cost; @@ -465,7 +476,7 @@ static CommandCost ReplaceFreeUnit(Vehicle **single_unit, DoCommandFlag flags, b if ((flags & DC_EXEC) != 0) { /* Move the new vehicle behind the old */ - CmdMoveVehicle(new_v, old_v, DC_EXEC, false); + CmdMoveVehicle(new_v, old_v, DC_EXEC | DC_AUTOREPLACE, false); /* Take over cargo * Note: We do only transfer cargo from the old to the new vehicle. @@ -485,7 +496,7 @@ static CommandCost ReplaceFreeUnit(Vehicle **single_unit, DoCommandFlag flags, b /* If we are not in DC_EXEC undo everything */ if ((flags & DC_EXEC) == 0) { - Command::Do(DC_EXEC, new_v->index, false, false, INVALID_CLIENT_ID); + Command::Do(DC_EXEC | DC_AUTOREPLACE, new_v->index, false, false, INVALID_CLIENT_ID); } } @@ -507,6 +518,25 @@ struct ReplaceChainItem { Vehicle *GetVehicle() const { return new_veh == nullptr ? old_veh : new_veh; } }; +/** + * When replacing a ship in an extended depot, copy the direction as well. + * @param old_ship The ship being replaced. + * @param new_ship The new ship that will replace the old one. + */ +void CopyShipStatusInExtendedDepot(const Ship *old_ship, Ship *new_ship) +{ + assert(IsExtendedDepotTile(old_ship->tile)); + assert(old_ship->tile == new_ship->tile); + + new_ship->x_pos = old_ship->x_pos; + new_ship->y_pos = old_ship->y_pos; + new_ship->z_pos = old_ship->z_pos; + new_ship->state = old_ship->state; + new_ship->direction = old_ship->direction; + new_ship->rotation = old_ship->rotation; + new_ship->GetImage(new_ship->direction, EIT_ON_MAP, &new_ship->sprite_cache.sprite_seq); +} + /** * Replace a whole vehicle chain * @param chain vehicle chain to let autoreplace/renew operator on @@ -517,8 +547,11 @@ struct ReplaceChainItem { */ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon_removal, bool *nothing_to_do) { + assert(flags & DC_AUTOREPLACE); + Vehicle *old_head = *chain; assert(old_head->IsPrimaryVehicle()); + TileIndex tile = old_head->tile; CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, (Money)0); @@ -569,7 +602,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon } if (last_engine == nullptr) last_engine = append; - cost.AddCost(CmdMoveVehicle(append, new_head, DC_EXEC, false)); + cost.AddCost(CmdMoveVehicle(append, new_head, DC_EXEC | DC_AUTOREPLACE, false)); if (cost.Failed()) break; } if (last_engine == nullptr) last_engine = new_head; @@ -588,7 +621,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon if (RailVehInfo(append->engine_type)->railveh_type == RAILVEH_WAGON) { /* Insert wagon after 'last_engine' */ - CommandCost res = CmdMoveVehicle(append, last_engine, DC_EXEC, false); + CommandCost res = CmdMoveVehicle(append, last_engine, DC_EXEC | DC_AUTOREPLACE, false); /* When we allow removal of wagons, either the move failing due * to the train becoming too long, or the train becoming longer @@ -619,7 +652,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon assert(RailVehInfo(wagon->engine_type)->railveh_type == RAILVEH_WAGON); /* Sell wagon */ - [[maybe_unused]] CommandCost ret = Command::Do(DC_EXEC, wagon->index, false, false, INVALID_CLIENT_ID); + [[maybe_unused]] CommandCost ret = Command::Do(DC_EXEC | DC_AUTOREPLACE, wagon->index, false, false, INVALID_CLIENT_ID); assert(ret.Succeeded()); it->new_veh = nullptr; @@ -661,6 +694,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 :) */ @@ -682,7 +718,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon if ((flags & DC_EXEC) == 0) { for (auto it = std::rbegin(replacements); it != std::rend(replacements); ++it) { if (it->new_veh != nullptr) { - Command::Do(DC_EXEC, it->new_veh->index, false, false, INVALID_CLIENT_ID); + Command::Do(DC_EXEC | DC_AUTOREPLACE, it->new_veh->index, false, false, INVALID_CLIENT_ID); it->new_veh = nullptr; } } @@ -700,6 +736,11 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon cost.AddCost(CopyHeadSpecificThings(old_head, new_head, flags)); if (cost.Succeeded()) { + /* Copy position and direction for ships in extended depots. */ + if (old_head->type == VEH_SHIP && IsExtendedDepotTile(old_head->tile)) { + CopyShipStatusInExtendedDepot(Ship::From(old_head), Ship::From(new_head)); + } + /* The new vehicle is constructed, now take over cargo */ if ((flags & DC_EXEC) != 0) { TransferCargo(old_head, new_head, true); @@ -714,7 +755,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon /* If we are not in DC_EXEC undo everything */ if ((flags & DC_EXEC) == 0) { - Command::Do(DC_EXEC, new_head->index, false, false, INVALID_CLIENT_ID); + Command::Do(DC_EXEC | DC_AUTOREPLACE, new_head->index, false, false, INVALID_CLIENT_ID); } } } @@ -766,45 +807,78 @@ CommandCost CmdAutoreplaceVehicle(DoCommandFlag flags, VehicleID veh_id) any_replacements |= (e != INVALID_ENGINE); w = (!free_wagon && w->type == VEH_TRAIN ? Train::From(w)->GetNextUnit() : nullptr); } + if (!any_replacements) return_cmd_error(STR_ERROR_AUTOREPLACE_NOTHING_TO_DO); CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, (Money)0); bool nothing_to_do = true; + bool was_stopped = free_wagon || ((v->vehstatus & VS_STOPPED) != 0); - if (any_replacements) { - bool was_stopped = free_wagon || ((v->vehstatus & VS_STOPPED) != 0); + /* Stop the vehicle */ + if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, true)); + if (cost.Failed()) return cost; - /* Stop the vehicle */ - if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, true)); - if (cost.Failed()) return cost; + assert(free_wagon || v->IsStoppedInDepot()); + if (flags & DC_EXEC) v->StopServicing(); - assert(free_wagon || v->IsStoppedInDepot()); - - /* We have to construct the new vehicle chain to test whether it is valid. - * Vehicle construction needs random bits, so we have to save the random seeds - * to prevent desyncs and to replay newgrf callbacks during DC_EXEC */ - SavedRandomSeeds saved_seeds; - SaveRandomSeeds(&saved_seeds); - if (free_wagon) { - cost.AddCost(ReplaceFreeUnit(&v, flags & ~DC_EXEC, ¬hing_to_do)); - } else { - cost.AddCost(ReplaceChain(&v, flags & ~DC_EXEC, wagon_removal, ¬hing_to_do)); - } - RestoreRandomSeeds(saved_seeds); - - if (cost.Succeeded() && (flags & DC_EXEC) != 0) { - if (free_wagon) { - ret = ReplaceFreeUnit(&v, flags, ¬hing_to_do); - } else { - ret = ReplaceChain(&v, flags, wagon_removal, ¬hing_to_do); - } - assert(ret.Succeeded() && ret.GetCost() == cost.GetCost()); - } - - /* Restart the vehicle */ - if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, false)); + TrainPlacement train_placement; + if (v->type == VEH_TRAIN) { + train_placement.LiftTrain(Train::From(v), flags); + } else if (IsExtendedDepotTile(v->tile)) { + UpdateExtendedDepotReservation(v, false); } - if (cost.Succeeded() && nothing_to_do) cost = CommandCost(STR_ERROR_AUTOREPLACE_NOTHING_TO_DO); + /* Start autoreplacing the vehicle. */ + flags |= DC_AUTOREPLACE; + + /* We have to construct the new vehicle chain to test whether it is valid. + * Vehicle construction needs random bits, so we have to save the random seeds + * to prevent desyncs and to replay newgrf callbacks during DC_EXEC */ + SavedRandomSeeds saved_seeds; + SaveRandomSeeds(&saved_seeds); + if (free_wagon) { + cost.AddCost(ReplaceFreeUnit(&v, flags & ~DC_EXEC, ¬hing_to_do)); + } else { + cost.AddCost(ReplaceChain(&v, flags & ~DC_EXEC, wagon_removal, ¬hing_to_do)); + } + RestoreRandomSeeds(saved_seeds); + + if (cost.Succeeded() && (flags & DC_EXEC) != 0) { + if (free_wagon) { + ret = ReplaceFreeUnit(&v, flags, ¬hing_to_do); + } else { + ret = ReplaceChain(&v, flags, wagon_removal, ¬hing_to_do); + } + + assert(ret.Succeeded()); + assert(ret.GetCost() == cost.GetCost()); + } + + /* Check whether the train can be placed on tracks. */ + bool platform_error = false; + + /* Autoreplacing is done. */ + flags &= ~DC_AUTOREPLACE; + + if (v->type == VEH_TRAIN) { + if (cost.Succeeded() && (flags & DC_EXEC) != 0) { + train_placement.LookForPlaceInDepot(Train::From(v), false); + if (train_placement.info < PI_WONT_LEAVE) { + platform_error = true; + if (v->owner == _local_company && v->IsFrontEngine()) { + SetDParam(0, v->index); + AddVehicleAdviceNewsItem(STR_ADVICE_PLATFORM_TYPE + train_placement.info - PI_ERROR_BEGIN, v->index); + } + } + } + train_placement.PlaceTrain(Train::From(v), flags); + } else if (IsExtendedDepotTile(v->tile)) { + UpdateExtendedDepotReservation(v, true); + } + + /* Restart the vehicle */ + if (!platform_error && !was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, false)); + + assert(cost.Failed() || !nothing_to_do); return cost; } diff --git a/src/base_station_base.h b/src/base_station_base.h index 0c0ad22770..de8688006e 100644 --- a/src/base_station_base.h +++ b/src/base_station_base.h @@ -140,24 +140,6 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> { */ virtual void GetTileArea(TileArea *ta, StationType type) const = 0; - - /** - * Obtain the length of a platform - * @pre tile must be a rail station tile - * @param tile A tile that contains the platform in question - * @return The length of the platform - */ - virtual uint GetPlatformLength(TileIndex tile) const = 0; - - /** - * Determines the REMAINING length of a platform, starting at (and including) - * the given tile. - * @param tile the tile from which to start searching. Must be a rail station tile - * @param dir The direction in which to search. - * @return The platform length - */ - virtual uint GetPlatformLength(TileIndex tile, DiagDirection dir) const = 0; - /** * Get the base station belonging to a specific tile. * @param tile The tile to get the base station from. diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 4d9a2ef647..77afa780f4 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" @@ -1167,8 +1168,8 @@ enum BuildVehicleHotkeys { 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 +1202,12 @@ 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); + if (this->listview_mode) depot_id -= type; + this->window_number = depot_id; this->sel_engine = INVALID_ENGINE; @@ -1240,16 +1242,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(depot_id); 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,28 +1263,22 @@ 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: + this->filter.railtypes = this->listview_mode ? INVALID_RAILTYPES : depot->r_types.rail_types; + break; + case VEH_AIRCRAFT: break; } @@ -1326,7 +1319,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 +1394,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 +1454,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; @@ -1479,18 +1472,17 @@ struct BuildVehicleWindow : Window { EngineID sel_id = INVALID_ENGINE; this->eng_list.clear(); - for (const Engine *e : Engine::IterateType(VEH_SHIP)) { - if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue; - EngineID eid = e->index; - if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue; + if (this->listview_mode || this->filter.railtypes != RAILTYPES_NONE) { + for (const Engine *e : Engine::IterateType(VEH_SHIP)) { + if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue; + EngineID eid = e->index; + if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue; + this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0); - /* Filter by name or NewGRF extra text */ - if (!FilterByText(e)) continue; - - this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0); - - if (eid == this->sel_engine) sel_id = eid; + if (eid == this->sel_engine) sel_id = eid; + } } + this->SelectEngine(sel_id); } @@ -1501,7 +1493,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, @@ -1606,6 +1598,40 @@ struct BuildVehicleWindow : Window { return list; } + void BuildVehicle() + { + EngineID sel_eng = this->sel_engine; + if (sel_eng == INVALID_ENGINE) return; + + 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, depot->xy, sel_eng, true, cargo, INVALID_CLIENT_ID); + } else { + 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. */ + bool refresh = false; + EngineID parent = sel_eng; + while (parent != INVALID_ENGINE) { + Engine *e = Engine::Get(parent); + refresh |= (e->display_last_variant != sel_eng); + e->display_last_variant = sel_eng; + parent = e->info.variant_id; + } + + if (refresh) { + InvalidateWindowData(WC_REPLACE_VEHICLE, this->vehicle_type, 0); // Update the autoreplace window + InvalidateWindowClassesData(WC_BUILD_VEHICLE); // The build windows needs updating as well + } + } + void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override { switch (widget) { @@ -1668,34 +1694,9 @@ struct BuildVehicleWindow : Window { break; } - case WID_BV_BUILD: { - EngineID sel_eng = this->sel_engine; - if (sel_eng != INVALID_ENGINE) { - CargoID cargo = this->cargo_filter_criteria; - if (cargo == CargoFilterCriteria::CF_ANY || cargo == CargoFilterCriteria::CF_ENGINES || cargo == CargoFilterCriteria::CF_NONE) cargo = INVALID_CARGO; - 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); - } else { - Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildPrimaryVehicle, this->window_number, sel_eng, true, cargo, INVALID_CLIENT_ID); - } - - /* Update last used variant in hierarchy and refresh if necessary. */ - bool refresh = false; - EngineID parent = sel_eng; - while (parent != INVALID_ENGINE) { - Engine *e = Engine::Get(parent); - refresh |= (e->display_last_variant != sel_eng); - e->display_last_variant = sel_eng; - parent = e->info.variant_id; - } - if (refresh) { - InvalidateWindowData(WC_REPLACE_VEHICLE, this->vehicle_type, 0); // Update the autoreplace window - InvalidateWindowClassesData(WC_BUILD_VEHICLE); // The build windows needs updating as well - return; - } - } + case WID_BV_BUILD: + this->BuildVehicle(); break; - } case WID_BV_RENAME: { EngineID sel_eng = this->sel_engine; @@ -1732,11 +1733,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); } @@ -1926,17 +1937,12 @@ 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)); + assert(depot_id == INVALID_DEPOT || Depot::IsValidID(depot_id)); - CloseWindowById(WC_BUILD_VEHICLE, num); + CloseWindowById(WC_BUILD_VEHICLE, depot_id != INVALID_DEPOT ? depot_id : (INVALID_DEPOT - 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..08eb0af1f1 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -15,9 +15,15 @@ #include "core/pool_func.hpp" #include "vehicle_gui.h" #include "vehiclelist.h" +#include "command_func.h" +#include "vehicle_base.h" +#include "viewport_kdtree.h" +#include "platform_func.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 +35,304 @@ 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()); + + InvalidateWindowData(WC_SELECT_DEPOT, this->veh_type); + + /* The sign will now disappear. */ + _viewport_sign_kdtree.Remove(ViewportSignKdtreeItem::MakeDepot(this->index)); + this->sign.MarkDirty(); +} + +/** + * Cancel deletion of this depot (reuse it). + * @param xy New location of the depot. + * @see Depot::IsInUse + * @see Depot::Disuse + */ +void Depot::Reuse(TileIndex xy) +{ + this->delete_ctr = 0; + this->xy = xy; + this->ta.tile = xy; + this->ta.h = this->ta.w = 1; + + /* Ensure the sign is not drawn */ + _viewport_sign_kdtree.Remove(ViewportSignKdtreeItem::MakeDepot(this->index)); + this->sign.MarkDirty(); +} + +/** + * Schedule deletion of this depot. + * + * This method is ought to be called after demolishing last depot part. + * The depot will be kept in the pool for a while so it can be + * placed again later without messing vehicle orders. + * + * @see Depot::IsInUse + * @see Depot::Reuse + */ +void Depot::Disuse() +{ + /* Mark that the depot is demolished and start the countdown. */ + this->delete_ctr = 8; + + /* Update the sign, it will be visible from now. */ + this->UpdateVirtCoord(); + _viewport_sign_kdtree.Insert(ViewportSignKdtreeItem::MakeDepot(this->index)); +} + +/** + * 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 free and closest (if none is free, just closest) part of depot to vehicle v. + */ +TileIndex Depot::GetBestDepotTile(Vehicle *v) const +{ + assert(this->veh_type == v->type); + TileIndex best_depot = INVALID_TILE; + DepotReservation best_found_type = DEPOT_RESERVATION_END; + uint best_distance = UINT_MAX; + + for (const auto &tile : this->depot_tiles) { + bool check_south = v->type == VEH_ROAD; + uint new_distance = DistanceManhattan(v->tile, tile); +again: + DepotReservation depot_reservation = GetDepotReservation(tile, check_south); + if (((best_found_type == depot_reservation) && new_distance < best_distance) || (depot_reservation < best_found_type)) { + best_depot = tile; + best_distance = new_distance; + best_found_type = depot_reservation; + } + if (check_south) { + /* For road vehicles, check north direction as well. */ + check_south = false; + goto again; + } + } + + 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 && this->IsInUse()) { + /* 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)); + } + + /* A max depot spread of 1 for VEH_SHIP is a special case, + * as ship depots consist of two tiles. */ + if (this->veh_type == VEH_SHIP && _settings_game.depot.depot_spread == 1) { + /* (ta.w, ta.h) must be equal to (1, 2) or (2, 1). + * This means that ta.w * ta.h must be equal to 2. */ + if (ta.w * ta.h != 2) return_cmd_error(STR_ERROR_DEPOT_TOO_SPREAD_OUT); + } else if (std::max(ta.w, 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); + } + + VehicleType veh_type = this->veh_type; + if (this->ta.tile != INVALID_TILE) { + this->RescanDepotTiles(); + assert(!this->depot_tiles.empty()); + this->xy = this->depot_tiles[0]; + InvalidateWindowData(WC_VEHICLE_DEPOT, this->index); + } else { + assert(this->IsInUse()); + this->Disuse(); + TileIndex old_tile = this->xy; + this->RescanDepotTiles(); + assert(this->depot_tiles.empty()); + this->xy = old_tile; + } + + InvalidateWindowData(WC_VEHICLE_DEPOT, this->index); + InvalidateWindowData(WC_SELECT_DEPOT, veh_type); +} + +/** + * Check whether a tile is a destination tile, such as the starting tiles of + * rail platforms (and not the middle tiles of the platforms). + * @param dep The depot being checked + * @param tile The tile being checked + * @return Whether the tile is of the given depot. + */ +bool IsDepotDestTile(Depot *dep, TileIndex tile) +{ + assert(IsDepotTile(tile)); + assert(GetDepotIndex(tile) == dep->index); + + switch (dep->veh_type) { + case VEH_TRAIN: + assert(IsRailDepotTile(tile)); + return !IsExtendedRailDepot(tile) || IsAnyStartPlatformTile(tile); + case VEH_ROAD: + case VEH_SHIP: + case VEH_AIRCRAFT: + return true; + default: NOT_REACHED(); + } +} + +/** + * 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; + if (IsDepotDestTile(this, tile)) 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); + } +} + +/** + * Fix tile reservations and vehicle on extended depots. + * @param v Vehicle to be checked. + * @param reserve Whether to reserve or free the position v is occupying. + */ +void UpdateExtendedDepotReservation(Vehicle *v, bool reserve) +{ + assert(v != nullptr); + assert(IsExtendedDepotTile(v->tile)); + DepotReservation res_type = DEPOT_RESERVATION_EMPTY; + + if (reserve) { + res_type = (v->vehstatus & VS_STOPPED) ? + DEPOT_RESERVATION_FULL_STOPPED_VEH : DEPOT_RESERVATION_IN_USE; + } + + switch (v->type) { + case VEH_ROAD: { + assert(v == v->First()); + assert(IsDiagonalDirection(v->direction)); + bool is_facing_south = IsDiagDirFacingSouth(DirToDiagDir(v->direction)); + TileArea ta; + for (Vehicle *u = v; u != nullptr; u = u->Next()) ta.Add(u->tile); + for (TileIndex t : ta) { + res_type = DEPOT_RESERVATION_EMPTY; + + if (reserve) { + res_type = (v->vehstatus & VS_STOPPED) ? + DEPOT_RESERVATION_FULL_STOPPED_VEH : DEPOT_RESERVATION_IN_USE; + } + for (Vehicle *rv : Vehicle::Iterate()) { + if (res_type == DEPOT_RESERVATION_FULL_STOPPED_VEH) break; + if (rv->IsInDepot() && ta.Contains(rv->tile) && rv->First() != v) { + assert(rv->type == v->type); + [[maybe_unused]] DiagDirection diag_dir = DirToDiagDir(rv->direction); + assert(DiagDirToAxis(DirToDiagDir(v->direction)) == DiagDirToAxis(diag_dir)); + if (is_facing_south == IsDiagDirFacingSouth(DirToDiagDir(rv->direction))) { + res_type = (rv->vehstatus & VS_STOPPED) ? + DEPOT_RESERVATION_FULL_STOPPED_VEH : DEPOT_RESERVATION_IN_USE; + } + } + } + SetDepotReservation(t, res_type, is_facing_south); + } + break; + } + + case VEH_SHIP: + SetDepotReservation(v->tile, res_type); + break; + + case VEH_TRAIN: { + DiagDirection dir = GetRailDepotDirection(v->tile); + SetDepotReservation(GetPlatformExtremeTile(v->tile, dir), res_type); + SetDepotReservation(GetPlatformExtremeTile(v->tile, ReverseDiagDir(dir)), res_type); + break; + } + + case VEH_AIRCRAFT: + break; + + default: NOT_REACHED(); + } } diff --git a/src/depot_base.h b/src/depot_base.h index 1d8330fc74..f25d817999 100644 --- a/src/depot_base.h +++ b/src/depot_base.h @@ -11,12 +11,18 @@ #define DEPOT_BASE_H #include "depot_map.h" +#include "viewport_type.h" #include "core/pool_type.hpp" #include "timer/timer_game_calendar.h" +#include "rail_type.h" +#include "road_type.h" 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 +31,37 @@ 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. + uint8_t delete_ctr; ///< Delete counter. If greater than 0 then it is decremented until it reaches 0; the depot is then deleted. + ViewportSign sign; ///< NOSAVE: Dimensions of sign + 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? @@ -41,8 +70,35 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> { */ inline bool IsOfType(const Depot *d) const { - return GetTileType(d->xy) == GetTileType(this->xy); + return d->veh_type == this->veh_type; } + + /** + * Check whether the depot currently is in use; in use means + * that it is not scheduled for deletion and that it still has + * a building on the map. Otherwise the building is demolished + * and the depot awaits to be deleted. + * @return true iff still in use + * @see Depot::Disuse + * @see Depot::Reuse + */ + inline bool IsInUse() const + { + return this->delete_ctr == 0; + } + + void Reuse(TileIndex xy); + void Disuse(); + void UpdateVirtCoord(); + + /* 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..1364800dd8 100644 --- a/src/depot_cmd.cpp +++ b/src/depot_cmd.cpp @@ -17,6 +17,10 @@ #include "vehiclelist.h" #include "window_func.h" #include "depot_cmd.h" +#include "strings_func.h" +#include "landscape.h" +#include "viewport_kdtree.h" +#include "timer/timer_game_tick.h" #include "table/strings.h" @@ -48,7 +52,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(); @@ -59,6 +63,8 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str } if (flags & DC_EXEC) { + /* _viewport_sign_kdtree does not need to be updated, only in-use depots can be renamed */ + if (reset) { d->name.clear(); MakeDefaultName(d); @@ -68,11 +74,135 @@ 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(); } + +/** Update the virtual coords needed to draw the depot sign. */ +void Depot::UpdateVirtCoord() +{ + Point pt = RemapCoords2(TileX(this->xy) * TILE_SIZE, TileY(this->xy) * TILE_SIZE); + + pt.y -= 32 * ZOOM_BASE; + + SetDParam(0, this->veh_type); + SetDParam(1, this->index); + this->sign.UpdatePosition(pt.x, pt.y, STR_VIEWPORT_DEPOT, STR_VIEWPORT_DEPOT_TINY); + + SetWindowDirty(WC_VEHICLE_DEPOT, this->index); +} + +/** Update the virtual coords needed to draw the depot sign for all depots. */ +void UpdateAllDepotVirtCoords() +{ + /* Only demolished depots have signs. */ + for (Depot *d : Depot::Iterate()) if (!d->IsInUse()) d->UpdateVirtCoord(); +} + +/** + * Find a demolished depot close to a tile. + * @param ta Tile area to search for. + * @param type Depot type. + * @param cid Previous owner of the depot. + * @return The index of a demolished nearby depot, or INVALID_DEPOT if none. + */ +DepotID FindDeletedDepotCloseTo(TileArea ta, VehicleType type, CompanyID cid) +{ + for (Depot *depot : Depot::Iterate()) { + if (depot->IsInUse() || depot->veh_type != type || depot->owner != cid) continue; + if (ta.Contains(depot->xy)) return depot->index; + } + + return INVALID_DEPOT; +} + +void OnTick_Depot() +{ + if (_game_mode == GM_EDITOR) return; + + /* Clean up demolished depots. */ + for (Depot *d : Depot::Iterate()) { + if (d->IsInUse()) continue; + if ((TimerGameTick::counter + d->index) % Ticks::DEPOT_REMOVAL_TICKS != 0) continue; + if (--d->delete_ctr != 0) continue; + delete d; + } +} + + +/** + * 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) { + /* Check for close unused depots. */ + check_area.Expand(7); // total distance of 8 + closest_depot = FindDeletedDepotCloseTo(check_area, veh_type, _current_company); + } + + 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); + if (!depot->IsInUse() && (flags & DC_EXEC)) depot->Reuse(ta.tile); + return depot->BeforeAddTiles(ta); + } + + return CommandCost(); +} diff --git a/src/depot_func.h b/src/depot_func.h index 208e02110c..d1890c8728 100644 --- a/src/depot_func.h +++ b/src/depot_func.h @@ -10,13 +10,16 @@ #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); +void UpdateAllDepotVirtCoords(); /** * Find out if the slope of the tile is suitable to build a depot of given direction @@ -33,4 +36,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..bfc09cf5e5 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -29,9 +29,12 @@ #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 "train_placement.h" #include "widgets/depot_widget.h" @@ -78,6 +81,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), @@ -259,6 +263,7 @@ struct DepotWindow : Window { VehicleType type; bool generate_list; WidgetID hovered_widget; ///< Index of the widget being hovered during drag/drop. -1 if no drag is in progress. + std::vector problematic_vehicles; ///< Vector associated to vehicle_list, with a value of true for vehicles that cannot leave the depot. VehicleList vehicle_list; VehicleList wagon_list; uint unitnumber_digits; @@ -266,15 +271,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 +289,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(); } @@ -405,6 +413,11 @@ struct DepotWindow : Window { for (; num < maxval; ir = ir.Translate(0, this->resize.step_height)) { // Draw the rows Rect cell = ir; /* Keep track of horizontal cells */ for (uint i = 0; i < this->num_columns && num < maxval; i++, num++) { + /* Draw a dark red background if train cannot be placed. */ + if (this->type == VEH_TRAIN && this->problematic_vehicles[num] == 1) { + GfxFillRect(cell.left, cell.top, cell.right, cell.bottom, PC_DARK_GREY); + } + /* Draw all vehicles in the current row */ const Vehicle *v = this->vehicle_list[num]; this->DrawVehicleInDepot(v, cell); @@ -426,7 +439,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,12 +721,23 @@ 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 */ BuildDepotVehicleList(this->type, this->window_number, &this->vehicle_list, &this->wagon_list); this->generate_list = false; DepotSortList(&this->vehicle_list); + if (this->type == VEH_TRAIN) { + this->problematic_vehicles.clear(); + TrainPlacement tp; + for (uint num = 0; num < this->vehicle_list.size(); ++num) { + const Vehicle *v = this->vehicle_list[num]; + this->problematic_vehicles.push_back(!tp.CanFindAppropriatePlatform(Train::From(v), false)); + } + } uint new_unitnumber_digits = GetUnitNumberDigits(this->vehicle_list); /* Only increase the size; do not decrease to prevent constant changes */ @@ -742,8 +766,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 +782,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 +814,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(STR_ERROR_CAN_T_START_STOP_VEHICLES, tile, widget == WID_D_START_ALL, false, vli); break; } @@ -810,7 +840,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 +851,11 @@ struct DepotWindow : Window { break; case WID_D_VEHICLE_LIST: - ShowVehicleListWindow(GetTileOwner(this->window_number), this->type, (TileIndex)this->window_number); + ShowVehicleListWindow(this->owner, this->type, this->window_number); break; case WID_D_AUTOREPLACE: - Command::Post(this->window_number, this->type); + Command::Post(STR_ERROR_CAN_T_REPLACE_VEHICLES, tile, this->type); break; } @@ -836,7 +866,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 +932,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 +1141,34 @@ 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); + if (!d->IsInUse()) return; + 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 +1185,357 @@ 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); + + /* Add reusable depots. */ + ta.Expand(8); + for (Depot *d : Depot::Iterate()) { + if (d->IsInUse()) continue; + if (d->veh_type != veh_type || d->owner != _current_company) continue; + if (!ta.Contains(d->xy)) continue; + _depots_nearby_list.push_back(d->index); + } + + 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. */ + const Depot *depot = FindDepotsNearby(ta, veh_type, false); + return depot == nullptr && (_settings_game.depot.adjacent_depots || std::any_of(std::begin(_depots_nearby_list), std::end(_depots_nearby_list), [](DepotID s) { return s != NEW_DEPOT; })); +} + +/** + * 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..da3e1804c9 100644 --- a/src/depot_map.h +++ b/src/depot_map.h @@ -12,24 +12,25 @@ #include "station_map.h" +static const uint8_t DEPOT_TYPE = 0x02; + /** * Check if a tile is a depot and it is a depot of the given type. */ inline bool IsDepotTypeTile(Tile tile, TransportType type) { + if (type == TRANSPORT_AIR) return IsHangarTile(tile); + + if (GB(tile.m5(), 6, 2) != DEPOT_TYPE) return false; + switch (type) { default: NOT_REACHED(); case TRANSPORT_RAIL: - return IsRailDepotTile(tile); - + return IsTileType(tile, MP_RAILWAY); case TRANSPORT_ROAD: - return IsRoadDepotTile(tile); - + return IsTileType(tile, MP_ROAD); case TRANSPORT_WATER: - return IsShipDepotTile(tile); - - case TRANSPORT_AIR: - return IsHangarTile(tile); + return IsTileType(tile, MP_WATER); } } @@ -40,19 +41,28 @@ inline bool IsDepotTypeTile(Tile tile, TransportType type) */ inline bool IsDepotTile(Tile tile) { - return IsRailDepotTile(tile) || IsRoadDepotTile(tile) || IsShipDepotTile(tile) || IsHangarTile(tile); + TileType type = GetTileType(tile); + if (type == MP_STATION) return IsHangar(tile); + if (GB(tile.m5(), 6, 2) != DEPOT_TYPE) return false; + + return type == MP_RAILWAY || type == MP_ROAD || type == MP_WATER; } +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(); } @@ -73,4 +83,98 @@ inline VehicleType GetDepotVehicleType(Tile t) } } +/** Return true if a tile belongs to an extended depot. */ +static inline bool IsExtendedDepot(Tile tile) { + assert(IsValidTile(tile)); + assert(IsDepotTile(tile)); + if (IsAirportTile(tile)) return false; + return HasBit(tile.m5(), 5); +} + +/** Return true if a tile belongs to an extended depot. */ +static inline bool IsExtendedDepotTile(TileIndex tile) { + if (!IsValidTile(tile)) return false; + if (!IsDepotTile(tile)) return false; + return IsExtendedDepot(tile); +} + +/** + * Has this depot some vehicle servicing or stopped inside? + * @param tile tile of the depot. + * @param south_dir In case of road transport, return reservation facing south if true. + * @return The type of reservation on this tile (empty, servicing or occupied). + * @pre is a depot tile + */ +static inline DepotReservation GetDepotReservation(Tile t, bool south_dir = false) +{ + assert(IsDepotTile(t)); + if (!IsExtendedDepot(t)) return DEPOT_RESERVATION_EMPTY; + if (south_dir) { + assert(GetDepotVehicleType(t) == VEH_ROAD); + return (DepotReservation)GB(t.m6(), 4, 2); + } + return (DepotReservation)GB(t.m4(), 6, 2); +} + +/** + * Is this a platform/depot tile full with stopped vehicles? + * @param tile tile of the depot. + * @param south_dir In case of road transport, check reservation facing south if true. + * @return the type of reservation of the depot. + * @pre is a depot tile + */ +static inline bool IsDepotFullWithStoppedVehicles(TileIndex t, bool south_dir = false) +{ + assert(IsDepotTile(t)); + if (!IsExtendedDepot(t)) return false; + return GetDepotReservation(t, south_dir) == DEPOT_RESERVATION_FULL_STOPPED_VEH; +} + + +/** + * Has this depot tile/platform some vehicle inside? + * @param tile tile of the depot. + * @param south_dir In case of road transport, check reservation facing south if true. + * @return true iff depot tile/platform has no vehicle. + * @pre IsExtendedDepotTile + */ +static inline bool IsExtendedDepotEmpty(TileIndex t, bool south_dir = false) +{ + assert(IsExtendedDepotTile(t)); + return GetDepotReservation(t, south_dir) == DEPOT_RESERVATION_EMPTY; +} + +/** + * Mark whether this depot has a ship inside. + * @param tile of the depot. + * @param reservation type of reservation + * @param south_dir Whether to set south direction reservation. + * @pre tile is an extended ship depot. + */ +static inline void SetDepotReservation(Tile t, DepotReservation reservation, bool south_dir = false) +{ + assert(IsDepotTile(t)); + if (!IsExtendedDepot(t)) return; + switch (GetTileType(t)) { + default: NOT_REACHED(); + case MP_RAILWAY: + break; + case MP_ROAD: + if (south_dir) { + SB(t.m6(), 4, 2, reservation); + return; + } + break; + case MP_WATER: + assert(GetDepotReservation(t) == GetDepotReservation(GetOtherShipDepotTile(t))); + SB(Tile(GetOtherShipDepotTile(t)).m4(), 6, 2, reservation); + break; + case MP_STATION: return; + } + + SB(t.m4(), 6, 2, reservation); +} + +void UpdateExtendedDepotReservation(Vehicle *v, bool state); + #endif /* DEPOT_MAP_H */ diff --git a/src/depot_type.h b/src/depot_type.h index 4e61c1bcbd..9a3812799e 100644 --- a/src/depot_type.h +++ b/src/depot_type.h @@ -14,7 +14,16 @@ 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' +/** Type of reservation of extended ship depots. */ +enum DepotReservation { + DEPOT_RESERVATION_EMPTY = 0, ///< No vehicle servicing/stopped on depot tile/platform. + DEPOT_RESERVATION_IN_USE = 1, ///< At least a vehicle is in the depot, but the depot tile is not full of stopped vehicles. + DEPOT_RESERVATION_FULL_STOPPED_VEH = 2, ///< The depot tile/platform is full with stopped vehicles. + DEPOT_RESERVATION_END +}; + #endif /* DEPOT_TYPE_H */ diff --git a/src/direction_func.h b/src/direction_func.h index c554873a0d..6ea858264d 100644 --- a/src/direction_func.h +++ b/src/direction_func.h @@ -276,4 +276,14 @@ inline bool IsDiagonalDirection(Direction dir) return (dir & 1) != 0; } +/** + * Checks if a given DiagDirection is facing south. + * @param diag_dir Diagonal direction to check + * @return true iff the diagonal direction is facing south. + */ +static inline bool IsDiagDirFacingSouth(DiagDirection diag_dir) +{ + return diag_dir == DIAGDIR_SE || diag_dir == DIAGDIR_SW; +} + #endif /* DIRECTION_FUNC_H */ diff --git a/src/dock_gui.cpp b/src/dock_gui.cpp index ffbf5aa63e..2b2a369665 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,13 @@ 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->HasWidget(WID_DT_DEPOT) && this->IsWidgetLowered(WID_DT_DEPOT)) || + (this->HasWidget(WID_DT_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_DT_EXTENDED_DEPOT)))) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + } + this->Window::Close(); } @@ -127,6 +135,7 @@ struct BuildDocksToolbarWindow : Window { bool can_build = CanBuildVehicleInfrastructure(VEH_SHIP); this->SetWidgetsDisabledState(!can_build, WID_DT_DEPOT, + WID_DT_EXTENDED_DEPOT, WID_DT_STATION, WID_DT_BUOY); if (!can_build) { @@ -137,11 +146,13 @@ struct BuildDocksToolbarWindow : Window { if (_game_mode != GM_EDITOR) { if (!can_build) { /* Show in the tooltip why this button is disabled. */ - this->GetWidget(WID_DT_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); + if (this->HasWidget(WID_DT_DEPOT)) this->GetWidget(WID_DT_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); + if (this->HasWidget(WID_DT_EXTENDED_DEPOT)) this->GetWidget(WID_DT_EXTENDED_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_DT_STATION)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_DT_BUOY)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); } else { - this->GetWidget(WID_DT_DEPOT)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP); + if (this->HasWidget(WID_DT_DEPOT)) this->GetWidget(WID_DT_DEPOT)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP); + if (this->HasWidget(WID_DT_EXTENDED_DEPOT)) this->GetWidget(WID_DT_EXTENDED_DEPOT)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUILD_EXTENDED_DEPOT_TOOLTIP); this->GetWidget(WID_DT_STATION)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUILD_DOCK_TOOLTIP); this->GetWidget(WID_DT_BUOY)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUOY_TOOLTIP); } @@ -164,7 +175,10 @@ 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); + case WID_DT_EXTENDED_DEPOT: + if (HandlePlacePushButton(this, widget, SPR_CURSOR_SHIP_DEPOT, HT_RECT)) { + ShowBuildDocksDepotPicker(this); + } break; case WID_DT_STATION: // Build station button @@ -205,8 +219,16 @@ struct BuildDocksToolbarWindow : Window { 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_EXTENDED_DEPOT: { + 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 +282,17 @@ 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; + bool extended = this->last_clicked_widget == WID_DT_EXTENDED_DEPOT; + + auto proc = [=](DepotID join_to) -> bool { + return Command::Post(STR_ERROR_CAN_T_BUILD_SHIP_DEPOT, CcBuildDocks, start_tile, _ship_depot_direction, adjacent, extended, join_to, end_tile); + }; + + ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_SHIP); + break; + } default: break; } @@ -270,11 +303,18 @@ struct BuildDocksToolbarWindow : Window { { if (_game_mode != GM_EDITOR && this->IsWidgetLowered(WID_DT_STATION)) SetViewportCatchmentStation(nullptr, true); + if (_game_mode != GM_EDITOR && + ((this->HasWidget(WID_DT_DEPOT) && this->IsWidgetLowered(WID_DT_DEPOT)) || + (this->HasWidget(WID_DT_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_DT_EXTENDED_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); } @@ -322,6 +362,25 @@ struct BuildDocksToolbarWindow : Window { }, DockToolbarGlobalHotkeys}; }; +/** + * Add the depot icons depending on availability of construction. + * @return Panel with water depot buttons. + */ +static std::unique_ptr MakeNWidgetWaterDepot() +{ + auto hor = std::make_unique(); + + if (HasBit(_settings_game.depot.water_depot_types, 0)) { + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_DEPOT, SPR_IMG_SHIP_DEPOT, STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP)); + } + + if (HasBit(_settings_game.depot.water_depot_types, 1)) { + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_EXTENDED_DEPOT, SPR_IMG_SHIP_DEPOT, STR_WATERWAYS_TOOLBAR_BUILD_EXTENDED_DEPOT_TOOLTIP)); + } + + return hor; +} + /** * Nested widget parts of docks toolbar, game version. * Position of #WID_DT_RIVER widget has changed. @@ -337,7 +396,7 @@ static constexpr NWidgetPart _nested_build_docks_toolbar_widgets[] = { NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_LOCK), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_BUILD_LOCK, STR_WATERWAYS_TOOLBAR_BUILD_LOCKS_TOOLTIP), NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(5, 22), SetFill(1, 1), EndContainer(), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_DEMOLISH), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), - NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_DEPOT), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_SHIP_DEPOT, STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP), + NWidgetFunction(MakeNWidgetWaterDepot), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_STATION), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_SHIP_DOCK, STR_WATERWAYS_TOOLBAR_BUILD_DOCK_TOOLTIP), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_BUOY), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_BUOY, STR_WATERWAYS_TOOLBAR_BUOY_TOOLTIP), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_BUILD_AQUEDUCT), SetMinimalSize(23, 22), SetFill(0, 1), SetDataTip(SPR_IMG_AQUEDUCT, STR_WATERWAYS_TOOLBAR_BUILD_AQUEDUCT_TOOLTIP), @@ -514,10 +573,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 +591,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 +638,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 +648,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..9a9e54cfc2 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -55,6 +55,8 @@ #include "timer/timer.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" +#include "depot_base.h" +#include "platform_func.h" #include "table/strings.h" #include "table/pricebase.h" @@ -374,6 +376,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. */ @@ -1614,14 +1622,13 @@ static void ReserveConsist(Station *st, Vehicle *u, CargoArray *consist_capleft, * Update the vehicle's load_unload_ticks, the time it will wait until it tries to load or unload * again. Adjust for overhang of trains and set it at least to 1. * @param front The vehicle to be updated. - * @param st The station the vehicle is loading at. * @param ticks The time it would normally wait, based on cargo loaded and unloaded. */ -static void UpdateLoadUnloadTicks(Vehicle *front, const Station *st, int ticks) +static void UpdateLoadUnloadTicks(Vehicle *front, int ticks) { if (front->type == VEH_TRAIN && _settings_game.order.station_length_loading_penalty) { /* Each platform tile is worth 2 rail vehicles. */ - int overhang = front->GetGroundVehicleCache()->cached_total_length - st->GetPlatformLength(front->tile) * TILE_SIZE; + int overhang = front->GetGroundVehicleCache()->cached_total_length - GetPlatformLength(front->tile) * TILE_SIZE; if (overhang > 0) { ticks <<= 1; ticks += (overhang * ticks) / 8; @@ -1883,9 +1890,9 @@ static void LoadUnloadVehicle(Vehicle *front) SetBit(front->vehicle_flags, VF_STOP_LOADING); } - UpdateLoadUnloadTicks(front, st, new_load_unload_ticks); + UpdateLoadUnloadTicks(front, new_load_unload_ticks); } else { - UpdateLoadUnloadTicks(front, st, 20); // We need the ticks for link refreshing. + UpdateLoadUnloadTicks(front, 20); // We need the ticks for link refreshing. bool finished_loading = true; if (front->current_order.GetLoadType() & OLFB_FULL_LOAD) { if (front->current_order.GetLoadType() == OLF_FULL_LOAD_ANY) { diff --git a/src/economy_type.h b/src/economy_type.h index e4d0ea077a..a4c08c4919 100644 --- a/src/economy_type.h +++ b/src/economy_type.h @@ -241,8 +241,6 @@ static const int INVALID_PRICE_MODIFIER = MIN_PRICE_MODIFIER - 1; static const uint TUNNELBRIDGE_TRACKBIT_FACTOR = 4; /** Multiplier for how many regular track bits a level crossing counts. */ static const uint LEVELCROSSING_TRACKBIT_FACTOR = 2; -/** Multiplier for how many regular track bits a road depot counts. */ -static const uint ROAD_DEPOT_TRACKBIT_FACTOR = 2; /** Multiplier for how many regular track bits a bay stop counts. */ static const uint ROAD_STOP_TRACKBIT_FACTOR = 2; /** Multiplier for how many regular tiles a lock counts. */ diff --git a/src/ground_vehicle.cpp b/src/ground_vehicle.cpp index a6ab9758c0..5981d22b77 100644 --- a/src/ground_vehicle.cpp +++ b/src/ground_vehicle.cpp @@ -197,7 +197,10 @@ bool GroundVehicle::IsChainInDepot() const /* Check whether the rest is also already trying to enter the depot. */ for (; v != nullptr; v = v->Next()) { - if (!v->T::IsInDepot() || v->tile != this->tile) return false; + if (!v->T::IsInDepot()) return false; + assert(IsDepotTile(v->tile)); + assert(IsDepotTile(this->tile)); + assert(GetDepotIndex(this->tile) == GetDepotIndex(v->tile)); } return true; 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/landscape.cpp b/src/landscape.cpp index 7964e3ba02..5da1a2995f 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -1654,6 +1654,7 @@ bool GenerateLandscape(uint8_t mode) void OnTick_Town(); void OnTick_Trees(); void OnTick_Station(); +void OnTick_Depot(); void OnTick_Industry(); void OnTick_Companies(); @@ -1667,6 +1668,7 @@ void CallLandscapeTick() OnTick_Town(); OnTick_Trees(); OnTick_Station(); + OnTick_Depot(); OnTick_Industry(); } diff --git a/src/lang/english.txt b/src/lang/english.txt index 87a8d3c594..4abac89abe 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 @@ -908,6 +910,8 @@ STR_NEWS_TRAIN_IS_STUCK :{WHITE}{VEHICLE STR_NEWS_VEHICLE_IS_LOST :{WHITE}{VEHICLE} is lost STR_NEWS_VEHICLE_UNPROFITABLE_YEAR :{WHITE}{VEHICLE}'s profit last year was {CURRENCY_LONG} STR_NEWS_VEHICLE_UNPROFITABLE_PERIOD :{WHITE}{VEHICLE}'s profit last period was {CURRENCY_LONG} +STR_NEWS_VEHICLE_CAN_T_FIND_FREE_DEPOT :{WHITE}{VEHICLE} can't find a free depot +STR_NEWS_VEHICLE_TOO_LONG_FOR_SERVICING :{WHITE}{VEHICLE} couldn't service in short platform STR_NEWS_AIRCRAFT_DEST_TOO_FAR :{WHITE}{VEHICLE} can't get to the next destination because it is out of range STR_NEWS_ORDER_REFIT_FAILED :{WHITE}{VEHICLE} stopped because an ordered refit failed @@ -1462,6 +1466,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 +1620,28 @@ 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_RAIL_DEPOT_TYPES :Rail depot types: {STRING2} +STR_CONFIG_SETTING_RAIL_DEPOT_TYPES_HELPTEXT :Available rail depot types for construction for human players. +STR_CONFIG_SETTING_ROAD_DEPOT_TYPES :Road depot types: {STRING2} +STR_CONFIG_SETTING_ROAD_DEPOT_TYPES_HELPTEXT :Available road depot types for construction for human players. +STR_CONFIG_SETTING_WATER_DEPOT_TYPES :Water depot types: {STRING2} +STR_CONFIG_SETTING_WATER_DEPOT_TYPES_HELPTEXT :Available water depot types for construction for human players. +###length 3 +STR_CONFIG_SETTING_ONLY_STANDARD_DEPOT :Standard depots +STR_CONFIG_SETTING_ONLY_EXTENDED_DEPOT :Extended depots +STR_CONFIG_SETTING_BOTH_DEPOT_TYPES :Both depot types +###next-name-looks-similar + +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 +2164,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 +2791,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 @@ -2774,7 +2807,8 @@ STR_RAIL_TOOLBAR_MAGLEV_CONSTRUCTION_CAPTION :Maglev Construc STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK :{BLACK}Build railway track. Ctrl+Click to remove railway track. Also press Shift to show cost estimate only STR_RAIL_TOOLBAR_TOOLTIP_BUILD_AUTORAIL :{BLACK}Build railway track using the Autorail mode. Ctrl+Click to remove railway track. Also press Shift to show cost estimate only -STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT_FOR_BUILDING :{BLACK}Build train depot (for buying and servicing trains). Also press Shift to show cost estimate only +STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT :{BLACK}Build standard train depot (for buying and servicing trains). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only +STR_RAIL_TOOLBAR_TOOLTIP_BUILD_EXTENDED_TRAIN_DEPOT :{BLACK}Build extended train depot (for buying and servicing trains). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL_TO_WAYPOINT :{BLACK}Build waypoint on railway. Ctrl+Click to select another waypoint to join. Also press Shift to show cost estimate only STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_STATION :{BLACK}Build railway station. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_SIGNALS :{BLACK}Build signal on railway. Ctrl+Click to build the alternate signal style{}Click+Drag to fill the selected section of rail with signals at the chosen spacing. Ctrl+Click+Drag to fill signals up to the next junction, station, or signal. Also press Shift to show cost estimate only @@ -2885,8 +2919,10 @@ STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_SECTION :{BLACK}Build ro STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_SECTION :{BLACK}Build tramway section. Ctrl+Click to remove tramway section. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOROAD :{BLACK}Build road section using the Autoroad mode. Ctrl+Click to remove road section. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOTRAM :{BLACK}Build tramway section using the Autotram mode. Ctrl+Click to remove tramway section. Also press Shift to show cost estimate only -STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT :{BLACK}Build road vehicle depot (for buying and servicing vehicles). Also press Shift to show cost estimate only -STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT :{BLACK}Build tram vehicle depot (for buying and servicing vehicles). Also press Shift to show cost estimate only +STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT :{BLACK}Build standard road vehicle depot (for buying and servicing vehicles). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only +STR_ROAD_TOOLBAR_TOOLTIP_BUILD_EXTENDED_ROAD_VEHICLE_DEPOT :{BLACK}Build extended road vehicle depot (for buying and servicing vehicles). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only +STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT :{BLACK}Build standard tram vehicle depot (for buying and servicing vehicles). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only +STR_ROAD_TOOLBAR_TOOLTIP_BUILD_EXTENDED_TRAM_VEHICLE_DEPOT :{BLACK}Build extended tram vehicle depot (for buying and servicing vehicles). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_ROAD_TO_WAYPOINT :{BLACK}Build waypoint on road. Ctrl+Click to select another waypoint to join. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_TRAM_TO_WAYPOINT :{BLACK}Build waypoint on tramway. Ctrl+Click to select another waypoint to join. Also press Shift to show cost estimate only STR_ROAD_TOOLBAR_TOOLTIP_BUILD_BUS_STATION :{BLACK}Build bus station. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only @@ -2927,7 +2963,8 @@ STR_WATERWAYS_TOOLBAR_CAPTION :{WHITE}Waterway STR_WATERWAYS_TOOLBAR_CAPTION_SE :{WHITE}Waterways STR_WATERWAYS_TOOLBAR_BUILD_CANALS_TOOLTIP :{BLACK}Build canals. Also press Shift to show cost estimate only STR_WATERWAYS_TOOLBAR_BUILD_LOCKS_TOOLTIP :{BLACK}Build locks. Also press Shift to show cost estimate only -STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP :{BLACK}Build ship depot (for buying and servicing ships). Also press Shift to show cost estimate only +STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP :{BLACK}Build standard ship depot (for buying and servicing ships). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only +STR_WATERWAYS_TOOLBAR_BUILD_EXTENDED_DEPOT_TOOLTIP :{BLACK}Build extended ship depot (for buying and servicing ships). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only STR_WATERWAYS_TOOLBAR_BUILD_DOCK_TOOLTIP :{BLACK}Build ship dock. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only STR_WATERWAYS_TOOLBAR_BUOY_TOOLTIP :{BLACK}Place a buoy which can be used as a waypoint. Also press Shift to show cost estimate only STR_WATERWAYS_TOOLBAR_BUILD_AQUEDUCT_TOOLTIP :{BLACK}Build aqueduct. Also press Shift to show cost estimate only @@ -3140,12 +3177,14 @@ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_NOENTRYSIGNALS :Railway track w STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_PBSSIGNALS :Railway track with combo- and path signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_NOENTRYSIGNALS :Railway track with combo- and one-way path signals STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBS_NOENTRYSIGNALS :Railway track with path and one-way path signals -STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT :Railway train depot +STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT :Standard railway train depot +STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT_EXTENDED :Extended railway train depot STR_LAI_ROAD_DESCRIPTION_ROAD :Road STR_LAI_ROAD_DESCRIPTION_ROAD_WITH_STREETLIGHTS :Road with street lights STR_LAI_ROAD_DESCRIPTION_TREE_LINED_ROAD :Tree-lined road -STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT :Road vehicle depot +STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT :Standard road vehicle depot +STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT_EXTENDED :Extended road vehicle depot STR_LAI_ROAD_DESCRIPTION_ROAD_RAIL_LEVEL_CROSSING :Road/rail level crossing STR_LAI_ROAD_DESCRIPTION_TRAMWAY :Tramway @@ -3170,7 +3209,8 @@ STR_LAI_WATER_DESCRIPTION_CANAL :Canal STR_LAI_WATER_DESCRIPTION_LOCK :Lock STR_LAI_WATER_DESCRIPTION_RIVER :River STR_LAI_WATER_DESCRIPTION_COAST_OR_RIVERBANK :Coast or riverbank -STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT :Ship depot +STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT :Standard ship depot +STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT_EXTENDED :Extended ship depot # Industries come directly from their industry names @@ -4407,6 +4447,7 @@ STR_VEHICLE_STATUS_STOPPED :{RED}Stopped STR_VEHICLE_STATUS_TRAIN_STOPPING_VEL :{RED}{VELOCITY} - Stopping STR_VEHICLE_STATUS_TRAIN_NO_POWER :{RED}No power STR_VEHICLE_STATUS_TRAIN_STUCK :{ORANGE}Waiting for free path +STR_VEHICLE_STATUS_SERVICING :{LTBLUE}Servicing vehicle STR_VEHICLE_STATUS_AIRCRAFT_TOO_FAR :{ORANGE}Too far to next destination STR_VEHICLE_STATUS_HEADING_FOR_STATION_VEL :{LTBLUE}{1:VELOCITY} - Heading for {0:STATION} @@ -4894,6 +4935,8 @@ STR_PERCENT_DOWN_SMALL :{TINY_FONT}{WHI STR_PERCENT_DOWN :{WHITE}{NUM}%{DOWN_ARROW} STR_PERCENT_UP_DOWN_SMALL :{TINY_FONT}{WHITE}{NUM}%{UP_ARROW}{DOWN_ARROW} STR_PERCENT_UP_DOWN :{WHITE}{NUM}%{UP_ARROW}{DOWN_ARROW} +STR_SERVICING_INDICATOR_SMALL :{TINY_FONT}{LTBLUE}Servicing ({NUM}{NBSP}%) +STR_SERVICING_INDICATOR :{LTBLUE}Servicing ({NUM}{NBSP}%) STR_PERCENT_NONE_SMALL :{TINY_FONT}{WHITE}{NUM}% STR_PERCENT_NONE :{WHITE}{NUM}% @@ -5110,10 +5153,18 @@ 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_DEPOT_TYPE_NOT_AVAILABLE :{WHITE}... depot type not available +STR_ERROR_DEPOT_EXTENDING_PLATFORMS :{WHITE}Extending already reserved depot platforms +STR_ERROR_DEPOT_EXTENDED_RAIL_DEPOT_IS_NOT_FREE :{WHITE}Extended rail depot has a reserved tile and can't be converted + +STR_ERROR_CAN_T_START_STOP_VEHICLES :{WHITE}Can't start at least one vehicle in the depot... +STR_ERROR_CAN_T_REPLACE_VEHICLES :{WHITE}Can't replace vehicles in the 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 +5174,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,8 +5183,19 @@ 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 +STR_ERROR_CAN_T_START_PLATFORM_TYPE :{WHITE}{VEHICLE} can't be started because there is no compatible platform in the depot for this type of train +STR_ERROR_CAN_T_START_PLATFORM_LONG :{WHITE}{VEHICLE} can't be started because compatible platforms are not long enough +STR_ERROR_DEPOT_FULL_DEPOT :There is no free depot compatible with this type of vehicle +###length 5 +STR_ADVICE_PLATFORM_TYPE :{WHITE}{VEHICLE} can't leave depot because there is no compatible platform for this type of train +STR_ADVICE_PLATFORM_LONG :{WHITE}{VEHICLE} can't leave depot because compatible platforms are not long enough +STR_ADVICE_VEHICLE_HAS_NO_POWER :{WHITE}{VEHICLE} can't leave depot because it has no power in any tile of the depot +STR_ADVICE_PLATFORM_FREE_PLATFORM :{WHITE}{VEHICLE} can't leave depot because compatible platforms are occupied +STR_ADVICE_PLATFORM_SIGNALS :{WHITE}{VEHICLE} can't leave depot because the segments of the compatible free platforms are occupied. Check the signaling of those segments. # Depot unbunching related errors STR_ERROR_UNBUNCHING_ONLY_ONE_ALLOWED :{WHITE}... can only have one unbunching order @@ -5297,6 +5360,7 @@ STR_ERROR_TOO_MANY_VEHICLES_IN_GAME :{WHITE}Too many STR_ERROR_CAN_T_CHANGE_SERVICING :{WHITE}Can't change servicing interval... STR_ERROR_VEHICLE_IS_DESTROYED :{WHITE}... vehicle is destroyed +STR_ERROR_NO_FREE_DEPOT :{WHITE}... there is no free depot STR_ERROR_CAN_T_CLONE_VEHICLE_LIST :{WHITE}... not all vehicles are identical @@ -5314,6 +5378,7 @@ STR_ERROR_NO_TOWN_ROADTYPES_AVAILABLE_YET_EXPLANATION :{WHITE}Start a STR_ERROR_CAN_T_MAKE_TRAIN_PASS_SIGNAL :{WHITE}Can't make train pass signal at danger... STR_ERROR_CAN_T_REVERSE_DIRECTION_TRAIN :{WHITE}Can't reverse direction of train... STR_ERROR_TRAIN_START_NO_POWER :Train has no power +STR_ERROR_ROAD_VEHICLE_START_NO_POWER :Road vehicle has no power STR_ERROR_CAN_T_MAKE_ROAD_VEHICLE_TURN :{WHITE}Can't make road vehicle turn around... @@ -5825,6 +5890,9 @@ STR_VIEWPORT_STATION_TINY :{TINY_FONT}{STA STR_VIEWPORT_WAYPOINT :{WAYPOINT} STR_VIEWPORT_WAYPOINT_TINY :{TINY_FONT}{WAYPOINT} +STR_VIEWPORT_DEPOT :{DEPOT} +STR_VIEWPORT_DEPOT_TINY :{TINY_FONT}{DEPOT} + # Simple strings to get specific types of data STR_COMPANY_NAME :{COMPANY} STR_COMPANY_NAME_COMPANY_NUM :{COMPANY} {COMPANY_NUM} diff --git a/src/newgrf_station.cpp b/src/newgrf_station.cpp index af67fbf648..86462ff4b9 100644 --- a/src/newgrf_station.cpp +++ b/src/newgrf_station.cpp @@ -24,6 +24,7 @@ #include "newgrf_animation_base.h" #include "newgrf_class_func.h" #include "timer/timer_game_calendar.h" +#include "platform_func.h" #include "safeguards.h" 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_base.h b/src/order_base.h index 8f2f10c252..b223440c59 100644 --- a/src/order_base.h +++ b/src/order_base.h @@ -225,6 +225,7 @@ public: inline void SetMaxSpeed(uint16_t speed) { this->max_speed = speed; } bool ShouldStopAtStation(const Vehicle *v, StationID station) const; + bool ShouldStopAtDepot(DepotID depot) const; bool CanLoadOrUnload() const; bool CanLeaveWithCargo(bool has_cargo) const; diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 1d6743106e..c9000c18bc 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 || !dp->IsInUse()) 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)); @@ -2232,6 +2215,17 @@ bool Order::ShouldStopAtStation(const Vehicle *v, StationID station) const !(this->GetNonStopType() & (is_dest_station ? ONSF_NO_STOP_AT_DESTINATION_STATION : ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS)); } +/** + * Check whether the given vehicle should stop at the given depot. + * @param v the vehicle that might be stopping. + * @param depot the depot to stop at. + * @return true if the vehicle should stop. + */ +bool Order::ShouldStopAtDepot(DepotID depot) const +{ + return this->IsType(OT_GOTO_DEPOT) && this->dest == depot; +} + bool Order::CanLoadOrUnload() const { return (this->IsType(OT_GOTO_STATION) || this->IsType(OT_IMPLICIT)) && 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/follow_track.hpp b/src/pathfinder/follow_track.hpp index d384568b62..33439d9ae8 100644 --- a/src/pathfinder/follow_track.hpp +++ b/src/pathfinder/follow_track.hpp @@ -18,6 +18,7 @@ #include "../tunnelbridge_map.h" #include "../depot_map.h" #include "pathfinder_func.h" +#include "../platform_func.h" /** * Track follower helper template class (can serve pathfinders and vehicle @@ -46,7 +47,8 @@ struct CFollowTrackT bool m_is_tunnel; ///< last turn passed tunnel bool m_is_bridge; ///< last turn passed bridge ramp bool m_is_station; ///< last turn passed station - int m_tiles_skipped; ///< number of skipped tunnel or station tiles + bool m_is_extended_depot; ///< last turn passed depot + int m_tiles_skipped; ///< number of skipped tunnel, depot or station tiles ErrorCode m_err; RailTypes m_railtypes; @@ -80,7 +82,7 @@ struct CFollowTrackT m_new_tile = INVALID_TILE; m_new_td_bits = TRACKDIR_BIT_NONE; m_exitdir = INVALID_DIAGDIR; - m_is_station = m_is_bridge = m_is_tunnel = false; + m_is_station = m_is_bridge = m_is_tunnel = m_is_extended_depot = false; m_tiles_skipped = 0; m_err = EC_NONE; m_railtypes = railtype_override; @@ -99,7 +101,7 @@ struct CFollowTrackT { assert(IsTram()); // this function shouldn't be called in other cases - if (IsNormalRoadTile(tile)) { + if (IsNormalRoadTile(tile) || IsExtendedRoadDepotTile(tile)) { RoadBits rb = GetRoadBits(tile, RTT_TRAM); switch (rb) { case ROAD_NW: return DIAGDIR_NW; @@ -170,11 +172,11 @@ struct CFollowTrackT { if (!DoTrackMasking()) return true; - if (m_is_station) { - /* Check skipped station tiles as well. */ + if (m_is_station || m_is_extended_depot) { + /* Check skipped station and depot tiles as well. */ TileIndexDiff diff = TileOffsByDiagDir(m_exitdir); for (TileIndex tile = m_new_tile - diff * m_tiles_skipped; tile != m_new_tile; tile += diff) { - if (HasStationReservation(tile)) { + if ((m_is_station && HasStationReservation(tile)) || (m_is_extended_depot && HasDepotReservation(tile))) { m_new_td_bits = TRACKDIR_BIT_NONE; m_err = EC_RESERVED; return false; @@ -200,7 +202,7 @@ protected: /** Follow the m_exitdir from m_old_tile and fill m_new_tile and m_tiles_skipped */ inline void FollowTileExit() { - m_is_station = m_is_bridge = m_is_tunnel = false; + m_is_station = m_is_bridge = m_is_tunnel = m_is_extended_depot = false; m_tiles_skipped = 0; /* extra handling for tunnels and bridges in our direction */ @@ -224,9 +226,13 @@ protected: /* normal or station tile, do one step */ m_new_tile = TileAddByDiagDir(m_old_tile, m_exitdir); - /* special handling for stations */ - if (IsRailTT() && HasStationTileRail(m_new_tile)) { - m_is_station = true; + /* special handling for stations and multi-tile depots */ + if (IsRailTT()) { + if (HasStationTileRail(m_new_tile)) { + m_is_station = true; + } else if (IsExtendedRailDepotTile(m_new_tile)) { + m_is_extended_depot = true; + } } else if (IsRoadTT() && IsStationRoadStopTile(m_new_tile)) { m_is_station = true; } @@ -266,14 +272,16 @@ protected: } } - /* road depots can be also left in one direction only */ + /* road depots can be also left in one direction sometimes */ if (IsRoadTT() && IsDepotTypeTile(m_old_tile, TT())) { - DiagDirection exitdir = GetRoadDepotDirection(m_old_tile); - if (exitdir != m_exitdir) { + RoadTramType rtt = IsTram() ? RTT_TRAM : RTT_ROAD; + RoadBits rb = GetRoadBits(m_old_tile, rtt); + if ((rb & DiagDirToRoadBits(m_exitdir)) == ROAD_NONE) { m_err = EC_NO_WAY; return false; } } + return true; } @@ -300,18 +308,19 @@ protected: /* road and rail depots can also be entered from one direction only */ if (IsRoadTT() && IsDepotTypeTile(m_new_tile, TT())) { - DiagDirection exitdir = GetRoadDepotDirection(m_new_tile); - if (ReverseDiagDir(exitdir) != m_exitdir) { - m_err = EC_NO_WAY; - return false; - } /* don't try to enter other company's depots */ if (GetTileOwner(m_new_tile) != m_veh_owner) { m_err = EC_OWNER; return false; } + RoadTramType rtt = IsTram() ? RTT_TRAM : RTT_ROAD; + RoadBits rb = GetRoadBits(m_new_tile, rtt); + if ((rb & DiagDirToRoadBits(ReverseDiagDir(m_exitdir))) == ROAD_NONE) { + m_err = EC_NO_WAY; + return false; + } } - if (IsRailTT() && IsDepotTypeTile(m_new_tile, TT())) { + if (IsRailTT() && IsStandardRailDepotTile(m_new_tile)) { DiagDirection exitdir = GetRailDepotDirection(m_new_tile); if (ReverseDiagDir(exitdir) != m_exitdir) { m_err = EC_NO_WAY; @@ -368,14 +377,14 @@ protected: } } - /* special handling for rail stations - get to the end of platform */ - if (IsRailTT() && m_is_station) { - /* entered railway station - * get platform length */ - uint length = BaseStation::GetByTile(m_new_tile)->GetPlatformLength(m_new_tile, TrackdirToExitdir(m_old_td)); - /* how big step we must do to get to the last platform tile? */ - m_tiles_skipped = length - 1; - /* move to the platform end */ + /* special handling for rail platforms - get to the end of platform */ + if (IsRailTT() && (m_is_station || m_is_extended_depot)) { + /* Entered a platform. */ + assert(HasStationTileRail(m_new_tile) || IsExtendedRailDepotTile(m_new_tile)); + /* How big step we must do to get to the last platform tile? */ + m_tiles_skipped = GetPlatformLength(m_new_tile, TrackdirToExitdir(m_old_td)) - 1; + /* Move to the platform end. */ + TileIndexDiff diff = TileOffsByDiagDir(m_exitdir); diff *= m_tiles_skipped; m_new_tile = TileAdd(m_new_tile, diff); @@ -390,14 +399,29 @@ protected: { /* rail and road depots cause reversing */ if (!IsWaterTT() && IsDepotTypeTile(m_old_tile, TT())) { - DiagDirection exitdir = IsRailTT() ? GetRailDepotDirection(m_old_tile) : GetRoadDepotDirection(m_old_tile); + DiagDirection exitdir; + switch (TT()) { + case TRANSPORT_AIR: + return false; + case TRANSPORT_RAIL: + if (IsExtendedRailDepot(m_old_tile)) return false; + exitdir = GetRailDepotDirection(m_old_tile); + break; + case TRANSPORT_ROAD: { + if (GetRoadBits(m_old_tile, IsTram() ? RTT_TRAM : RTT_ROAD) != DiagDirToRoadBits(m_exitdir)) return false; + exitdir = ReverseDiagDir(m_exitdir); + break; + } + default: NOT_REACHED(); + } + if (exitdir != m_exitdir) { /* reverse */ m_new_tile = m_old_tile; m_new_td_bits = TrackdirToTrackdirBits(ReverseTrackdir(m_old_td)); m_exitdir = exitdir; m_tiles_skipped = 0; - m_is_tunnel = m_is_bridge = m_is_station = false; + m_is_tunnel = m_is_bridge = m_is_station = m_is_extended_depot = false; return true; } } diff --git a/src/pathfinder/pathfinder_func.h b/src/pathfinder/pathfinder_func.h index 444b100ce7..410776511b 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,46 @@ 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; + DepotReservation best_found_type = dep->veh_type == VEH_SHIP ? DEPOT_RESERVATION_END : DEPOT_RESERVATION_EMPTY; + uint best_distance = UINT_MAX; + + for (auto const &depot_tile : dep->depot_tiles) { + uint new_distance = DistanceManhattan(depot_tile, tile); + bool check_south_direction = dep->veh_type == VEH_ROAD; +again: + DepotReservation depot_reservation = GetDepotReservation(depot_tile, check_south_direction); + if (((best_found_type == depot_reservation) && new_distance < best_distance) || (depot_reservation < best_found_type)) { + best_tile = depot_tile; + best_distance = new_distance; + best_found_type = depot_reservation; + } + if (check_south_direction) { + check_south_direction = false; + goto again; + } + } + + 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_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index f6217d2b24..196403aa97 100644 --- a/src/pathfinder/yapf/yapf_costrail.hpp +++ b/src/pathfinder/yapf/yapf_costrail.hpp @@ -150,13 +150,24 @@ public: return false; } + /** Check for a reserved depot platform. */ + inline bool IsAnyDepotTileReserved(TileIndex tile, Trackdir trackdir, int skipped) + { + TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(trackdir))); + for (; skipped >= 0; skipped--, tile += diff) { + if (HasDepotReservation(tile)) return true; + } + return false; + } + /** The cost for reserved tiles, including skipped ones. */ inline int ReservationCost(Node &n, TileIndex tile, Trackdir trackdir, int skipped) { if (n.m_num_signals_passed >= m_sig_look_ahead_costs.size() / 2) return 0; if (!IsPbsSignal(n.m_last_signal_type)) return 0; - if (IsRailStationTile(tile) && IsAnyStationTileReserved(tile, trackdir, skipped)) { + if ((IsRailStationTile(tile) && IsAnyStationTileReserved(tile, trackdir, skipped)) || + (IsExtendedRailDepotTile(tile) && IsAnyDepotTileReserved(tile, trackdir, skipped))) { return Yapf().PfGetSettings().rail_pbs_station_penalty * (skipped + 1); } else if (TrackOverlapsTracks(GetReservedTrackbits(tile), TrackdirToTrack(trackdir))) { int cost = Yapf().PfGetSettings().rail_pbs_cross_penalty; @@ -389,13 +400,12 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th /* Tests for 'potential target' reasons to close the segment. */ if (cur.tile == prev.tile) { /* Penalty for reversing in a depot. */ - assert(IsRailDepot(cur.tile)); + assert(IsStandardRailDepot(cur.tile)); segment_cost += Yapf().PfGetSettings().rail_depot_reverse_penalty; - } else if (IsRailDepotTile(cur.tile)) { + } else if (IsStandardRailDepotTile(cur.tile)) { /* We will end in this pass (depot is possible target) */ end_segment_reason |= ESRB_DEPOT; - } else if (cur.tile_type == MP_STATION && IsRailWaypoint(cur.tile)) { if (v->current_order.IsType(OT_GOTO_WAYPOINT) && GetStationIndex(cur.tile) == v->current_order.GetDestination() && @@ -440,14 +450,14 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th /* Waypoint is also a good reason to finish. */ end_segment_reason |= ESRB_WAYPOINT; - } else if (tf->m_is_station) { + } else if (tf->m_is_station || tf->m_is_extended_depot) { /* Station penalties. */ uint platform_length = tf->m_tiles_skipped + 1; /* We don't know yet if the station is our target or not. Act like * if it is pass-through station (not our destination). */ segment_cost += Yapf().PfGetSettings().rail_station_penalty * platform_length; /* We will end in this pass (station is possible target) */ - end_segment_reason |= ESRB_STATION; + end_segment_reason |= ESRB_PLATFORM; } else if (TrackFollower::DoTrackMasking() && cur.tile_type == MP_RAILWAY) { /* Searching for a safe tile? */ @@ -591,13 +601,21 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th } } - /* Station platform-length penalty. */ - if ((end_segment_reason & ESRB_STATION) != ESRB_NONE) { - const BaseStation *st = BaseStation::GetByTile(n.GetLastTile()); - assert(st != nullptr); - uint platform_length = st->GetPlatformLength(n.GetLastTile(), ReverseDiagDir(TrackdirToExitdir(n.GetLastTrackdir()))); - /* Reduce the extra cost caused by passing-station penalty (each station receives it in the segment cost). */ + /* Platform-length penalty. */ + if ((end_segment_reason & ESRB_PLATFORM) != ESRB_NONE) { + assert(HasStationTileRail(n.GetLastTile()) || IsExtendedRailDepotTile(n.GetLastTile())); + uint platform_length = GetPlatformLength(n.GetLastTile(), ReverseDiagDir(TrackdirToExitdir(n.GetLastTrackdir()))); + /* Reduce the extra cost caused by passing-platform penalty (each platform receives it in the segment cost). */ extra_cost -= Yapf().PfGetSettings().rail_station_penalty * platform_length; + if (tf->m_is_extended_depot) { + DepotReservation depot_reservation = GetDepotReservation(n.GetLastTile()); + if (depot_reservation == DEPOT_RESERVATION_FULL_STOPPED_VEH) { + extra_cost += YAPF_INFINITE_PENALTY; + } else { + extra_cost += (HasDepotReservation(n.GetLastTile()) ? 2 : 1) * platform_length * Yapf().PfGetSettings().rail_station_penalty; + } + } + /* Add penalty for the inappropriate platform length. */ extra_cost += PlatformLengthPenalty(platform_length); } 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_rail.cpp b/src/pathfinder/yapf/yapf_rail.cpp index 9580086799..d88e88110f 100644 --- a/src/pathfinder/yapf/yapf_rail.cpp +++ b/src/pathfinder/yapf/yapf_rail.cpp @@ -16,6 +16,7 @@ #include "yapf_destrail.hpp" #include "../../viewport_func.h" #include "../../newgrf_station.h" +#include "../../platform_func.h" #include "../../safeguards.h" @@ -85,6 +86,23 @@ private: return true; } + /** Reserve a railway platform. Tile contains the failed tile on abort. */ + bool ReserveRailDepotPlatform(TileIndex &tile, DiagDirection dir) + { + assert(IsExtendedRailDepotTile(tile)); + TileIndex start = tile; + TileIndexDiff diff = TileOffsByDiagDir(dir); + + do { + if (HasDepotReservation(tile)) return false; + SetDepotReservation(tile, true); + MarkTileDirtyByTile(tile); + tile = TileAdd(tile, diff); + } while (IsCompatibleTrainDepotTile(tile, start) && tile != m_origin_tile); + + return true; + } + /** Try to reserve a single track/platform. */ bool ReserveSingleTrack(TileIndex tile, Trackdir td) { @@ -94,6 +112,12 @@ private: m_res_fail_tile = tile; m_res_fail_td = td; } + } else if (IsExtendedRailDepotTile(tile)) { + if (!ReserveRailDepotPlatform(tile, TrackdirToExitdir(ReverseTrackdir(td)))) { + /* Platform could not be reserved, undo. */ + m_res_fail_tile = tile; + m_res_fail_td = td; + } } else { if (!TryReserveRailTrack(tile, TrackdirToTrack(td))) { /* Tile couldn't be reserved, undo. */ @@ -116,6 +140,13 @@ private: SetRailStationReservation(tile, false); tile = TileAdd(tile, diff); } + } else if (IsExtendedRailDepotTile(tile)) { + TileIndex start = tile; + TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(td))); + while ((tile != m_res_fail_tile || td != m_res_fail_td) && IsCompatibleTrainDepotTile(tile, start)) { + SetDepotReservation(tile, false); + tile = TileAdd(tile, diff); + } } else if (tile != m_res_fail_tile || td != m_res_fail_td) { UnreserveRailTrack(tile, TrackdirToTrack(td)); } @@ -646,7 +677,9 @@ bool YapfTrainFindNearestSafeTile(const Train *v, TileIndex tile, Trackdir td, b /** if any track changes, this counter is incremented - that will invalidate segment cost cache */ int CSegmentCostCacheBase::s_rail_change_counter = 0; +extern void FixBigRailDepotSprites(Tile tile); void YapfNotifyTrackLayoutChange(TileIndex tile, Track track) { + FixBigRailDepotSprites(tile); CSegmentCostCacheBase::NotifyTrackLayoutChange(tile, track); } diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp index 209b64b52a..c1876dad13 100644 --- a/src/pathfinder/yapf/yapf_road.cpp +++ b/src/pathfinder/yapf/yapf_road.cpp @@ -66,6 +66,19 @@ protected: /* Increase the cost for level crossings */ if (IsLevelCrossing(tile)) { cost += Yapf().PfGetSettings().road_crossing_penalty; + } else if (IsRoadDepot(tile) && IsExtendedRoadDepot(tile)) { + switch (GetDepotReservation(tile, IsDiagDirFacingSouth(TrackdirToExitdir(trackdir)))) { + case DEPOT_RESERVATION_FULL_STOPPED_VEH: + cost += 16 * YAPF_TILE_LENGTH; + break; + case DEPOT_RESERVATION_IN_USE: + cost += 8 * YAPF_TILE_LENGTH; + break; + case DEPOT_RESERVATION_EMPTY: + cost += YAPF_TILE_LENGTH; + break; + default: NOT_REACHED(); + } } break; @@ -135,7 +148,7 @@ public: } /* stop if we have just entered the depot */ - if (IsRoadDepotTile(tile) && trackdir == DiagDirToDiagTrackdir(ReverseDiagDir(GetRoadDepotDirection(tile)))) { + if (IsRoadDepotTile(tile) && !IsExtendedRoadDepotTile(tile) && trackdir == DiagDirToDiagTrackdir(ReverseDiagDir(GetRoadDepotDirection(tile)))) { /* next time we will reverse and leave the depot */ break; } @@ -201,7 +214,7 @@ public: /** Called by YAPF to detect if node ends in the desired destination */ inline bool PfDetectDestination(Node &n) { - return IsRoadDepotTile(n.m_segment_last_tile); + return PfDetectDestinationTile(n.m_segment_last_tile, n.m_segment_last_td); } inline bool PfDetectDestinationTile(TileIndex tile, Trackdir) @@ -234,7 +247,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 +267,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 +308,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/pathfinder/yapf/yapf_ship.cpp b/src/pathfinder/yapf/yapf_ship.cpp index 57d5e9d877..b2b998ba8b 100644 --- a/src/pathfinder/yapf/yapf_ship.cpp +++ b/src/pathfinder/yapf/yapf_ship.cpp @@ -382,6 +382,10 @@ public: c += count * 3 * YAPF_TILE_LENGTH; } + if (IsShipDepotTile(n.GetTile())) { + if (IsExtendedDepot(n.GetTile()) && IsDepotFullWithStoppedVehicles(n.GetTile())) c += YAPF_INFINITE_PENALTY; + } + /* Skipped tile cost for aqueducts. */ c += YAPF_TILE_LENGTH * tf->m_tiles_skipped; diff --git a/src/pathfinder/yapf/yapf_type.hpp b/src/pathfinder/yapf/yapf_type.hpp index 4f301b0fb7..511f89be83 100644 --- a/src/pathfinder/yapf/yapf_type.hpp +++ b/src/pathfinder/yapf/yapf_type.hpp @@ -23,7 +23,7 @@ enum EndSegmentReason { ESR_CHOICE_FOLLOWS, ///< the next tile contains a choice (the track splits to more than one segments) ESR_DEPOT, ///< stop in the depot (could be a target next time) ESR_WAYPOINT, ///< waypoint encountered (could be a target next time) - ESR_STATION, ///< station encountered (could be a target next time) + ESR_PLATFORM, ///< platform (station/extended depot) encountered (could be a target next time) ESR_SAFE_TILE, ///< safe waiting position found (could be a target) /* The following reasons are used only internally by PfCalcCost(). @@ -47,7 +47,7 @@ enum EndSegmentReasonBits { ESRB_CHOICE_FOLLOWS = 1 << ESR_CHOICE_FOLLOWS, ESRB_DEPOT = 1 << ESR_DEPOT, ESRB_WAYPOINT = 1 << ESR_WAYPOINT, - ESRB_STATION = 1 << ESR_STATION, + ESRB_PLATFORM = 1 << ESR_PLATFORM, ESRB_SAFE_TILE = 1 << ESR_SAFE_TILE, ESRB_PATH_TOO_LONG = 1 << ESR_PATH_TOO_LONG, @@ -58,10 +58,10 @@ enum EndSegmentReasonBits { /* Additional (composite) values. */ /* What reasons mean that the target can be found and needs to be detected. */ - ESRB_POSSIBLE_TARGET = ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION | ESRB_SAFE_TILE, + ESRB_POSSIBLE_TARGET = ESRB_DEPOT | ESRB_WAYPOINT | ESRB_PLATFORM | ESRB_SAFE_TILE, /* What reasons can be stored back into cached segment. */ - ESRB_CACHED_MASK = ESRB_DEAD_END | ESRB_RAIL_TYPE | ESRB_INFINITE_LOOP | ESRB_SEGMENT_TOO_LONG | ESRB_CHOICE_FOLLOWS | ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION | ESRB_SAFE_TILE, + ESRB_CACHED_MASK = ESRB_DEAD_END | ESRB_RAIL_TYPE | ESRB_INFINITE_LOOP | ESRB_SEGMENT_TOO_LONG | ESRB_CHOICE_FOLLOWS | ESRB_DEPOT | ESRB_WAYPOINT | ESRB_PLATFORM | ESRB_SAFE_TILE, /* Reasons to abort pathfinding in this direction. */ ESRB_ABORT_PF_MASK = ESRB_DEAD_END | ESRB_PATH_TOO_LONG | ESRB_INFINITE_LOOP | ESRB_FIRST_TWO_WAY_RED, diff --git a/src/pbs.cpp b/src/pbs.cpp index 363404330c..27a366ff91 100644 --- a/src/pbs.cpp +++ b/src/pbs.cpp @@ -12,6 +12,8 @@ #include "vehicle_func.h" #include "newgrf_station.h" #include "pathfinder/follow_track.hpp" +#include "platform_func.h" +#include "depot_map.h" #include "safeguards.h" @@ -47,28 +49,6 @@ TrackBits GetReservedTrackbits(TileIndex t) return TRACK_BIT_NONE; } -/** - * Set the reservation for a complete station platform. - * @pre IsRailStationTile(start) - * @param start starting tile of the platform - * @param dir the direction in which to follow the platform - * @param b the state the reservation should be set to - */ -void SetRailStationPlatformReservation(TileIndex start, DiagDirection dir, bool b) -{ - TileIndex tile = start; - TileIndexDiff diff = TileOffsByDiagDir(dir); - - assert(IsRailStationTile(start)); - assert(GetRailStationAxis(start) == DiagDirToAxis(dir)); - - do { - SetRailStationReservation(tile, b); - MarkTileDirtyByTile(tile); - tile = TileAdd(tile, diff); - } while (IsCompatibleTrainStationTile(tile, start)); -} - /** * Try to reserve a specific track on a tile * @param tile the tile @@ -202,12 +182,12 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra /* No reservation --> path end found */ if (reserved == TRACKDIR_BIT_NONE) { - if (ft.m_is_station) { + if (ft.m_is_station || ft.m_is_extended_depot) { /* Check skipped station tiles as well, maybe our reservation ends inside the station. */ TileIndexDiff diff = TileOffsByDiagDir(ft.m_exitdir); while (ft.m_tiles_skipped-- > 0) { ft.m_new_tile -= diff; - if (HasStationReservation(ft.m_new_tile)) { + if ((ft.m_is_station && HasStationReservation(ft.m_new_tile)) || (ft.m_is_extended_depot && HasDepotReservation(ft.m_new_tile))) { tile = ft.m_new_tile; trackdir = DiagDirToDiagTrackdir(ft.m_exitdir); break; @@ -240,7 +220,7 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra if (tile == start_tile && trackdir == start_trackdir) break; } /* Depot tile? Can't continue. */ - if (IsRailDepotTile(tile)) break; + if (IsStandardRailDepotTile(tile)) break; /* Non-pbs signal? Reservation can't continue. */ if (IsTileType(tile, MP_RAILWAY) && HasSignalOnTrackdir(tile, trackdir) && !IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) break; } @@ -292,7 +272,7 @@ PBSTileInfo FollowTrainReservation(const Train *v, Vehicle **train_on_res) TileIndex tile = v->tile; Trackdir trackdir = v->GetVehicleTrackdir(); - if (IsRailDepotTile(tile) && !GetDepotReservationTrackBits(tile)) return PBSTileInfo(tile, trackdir, false); + if (IsStandardRailDepotTile(tile) && !GetDepotReservationTrackBits(tile)) return PBSTileInfo(tile, trackdir, false); FindTrainOnTrackInfo ftoti; ftoti.res = FollowReservation(v->owner, GetRailTypeInfo(v->railtype)->compatible_railtypes, tile, trackdir); @@ -300,14 +280,14 @@ PBSTileInfo FollowTrainReservation(const Train *v, Vehicle **train_on_res) if (train_on_res != nullptr) { FindVehicleOnPos(ftoti.res.tile, &ftoti, FindTrainOnTrackEnum); if (ftoti.best != nullptr) *train_on_res = ftoti.best->First(); - if (*train_on_res == nullptr && IsRailStationTile(ftoti.res.tile)) { - /* The target tile is a rail station. The track follower + if (*train_on_res == nullptr && (IsRailStationTile(ftoti.res.tile) || IsExtendedRailDepotTile(ftoti.res.tile))) { + /* The target tile is a rail station or extended depot. The track follower * has stopped on the last platform tile where we haven't * found a train. Also check all previous platform tiles * for a possible train. */ TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(ftoti.res.trackdir))); - for (TileIndex st_tile = ftoti.res.tile + diff; *train_on_res == nullptr && IsCompatibleTrainStationTile(st_tile, ftoti.res.tile); st_tile += diff) { - FindVehicleOnPos(st_tile, &ftoti, FindTrainOnTrackEnum); + for (TileIndex pt_tile = ftoti.res.tile + diff; *train_on_res == nullptr && IsCompatiblePlatformTile(pt_tile, ftoti.res.tile); pt_tile += diff) { + FindVehicleOnPos(pt_tile, &ftoti, FindTrainOnTrackEnum); if (ftoti.best != nullptr) *train_on_res = ftoti.best->First(); } } @@ -348,11 +328,11 @@ Train *GetTrainForReservation(TileIndex tile, Track track) FindVehicleOnPos(ftoti.res.tile, &ftoti, FindTrainOnTrackEnum); if (ftoti.best != nullptr) return ftoti.best; - /* Special case for stations: check the whole platform for a vehicle. */ - if (IsRailStationTile(ftoti.res.tile)) { + /* Special case for stations and extended depots: check the whole platform for a vehicle. */ + if (IsRailStationTile(ftoti.res.tile) || IsExtendedRailDepotTile(ftoti.res.tile)) { TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(ftoti.res.trackdir))); - for (TileIndex st_tile = ftoti.res.tile + diff; IsCompatibleTrainStationTile(st_tile, ftoti.res.tile); st_tile += diff) { - FindVehicleOnPos(st_tile, &ftoti, FindTrainOnTrackEnum); + for (TileIndex pt_tile = ftoti.res.tile + diff; IsCompatiblePlatformTile(pt_tile, ftoti.res.tile); pt_tile += diff) { + FindVehicleOnPos(pt_tile, &ftoti, FindTrainOnTrackEnum); if (ftoti.best != nullptr) return ftoti.best; } } @@ -379,7 +359,7 @@ Train *GetTrainForReservation(TileIndex tile, Track track) */ bool IsSafeWaitingPosition(const Train *v, TileIndex tile, Trackdir trackdir, bool include_line_end, bool forbid_90deg) { - if (IsRailDepotTile(tile)) return true; + if (IsStandardRailDepotTile(tile)) return true; if (IsTileType(tile, MP_RAILWAY)) { /* For non-pbs signals, stop on the signal tile. */ @@ -432,7 +412,7 @@ bool IsWaitingPositionFree(const Train *v, TileIndex tile, Trackdir trackdir, bo if (TrackOverlapsTracks(reserved, track)) return false; /* Not reserved and depot or not a pbs signal -> free. */ - if (IsRailDepotTile(tile)) return true; + if (IsStandardRailDepotTile(tile)) return true; if (IsTileType(tile, MP_RAILWAY) && HasSignalOnTrackdir(tile, trackdir) && !IsPbsSignal(GetSignalType(tile, track))) return true; /* Check the next tile, if it's a PBS signal, it has to be free as well. */ @@ -446,3 +426,26 @@ bool IsWaitingPositionFree(const Train *v, TileIndex tile, Trackdir trackdir, bo return !HasReservedTracks(ft.m_new_tile, TrackdirBitsToTrackBits(ft.m_new_td_bits)); } + +/** + * Fix the sprites of depots to show it opened or closed depending on its neighbours. + * @param t Tile that has changed. + */ +void FixBigRailDepotSprites(Tile t) +{ + if (t == INVALID_TILE) return; + + /* Expand tile area to check. */ + TileArea ta = TileArea(t).Expand(1); + + for (Tile tile : ta) { + if (!IsExtendedRailDepotTile(tile)) continue; + CFollowTrackRail ft(GetTileOwner(tile), GetRailTypeInfo(GetTileRailType(tile))->compatible_railtypes); + Track track = GetRailDepotTrack(tile); + Trackdir trackdir = TrackToTrackdir(track); + if (track == TRACK_X) trackdir = ReverseTrackdir(trackdir); + bool opened = ft.Follow(tile, trackdir); + if (track == TRACK_Y) opened = !opened; + SB(tile.m5(), 1, 1, opened); + } +} diff --git a/src/platform.cpp b/src/platform.cpp new file mode 100644 index 0000000000..55e0871d62 --- /dev/null +++ b/src/platform.cpp @@ -0,0 +1,406 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file platform.cpp Implementation of platform functions. */ + +#include "stdafx.h" +#include "station_map.h" +#include "platform_func.h" +#include "viewport_func.h" +#include "depot_base.h" +#include "vehicle_base.h" +#include "engine_base.h" + +/** + * Set the reservation for a complete station platform. + * @pre IsRailStationTile(start) + * @param start starting tile of the platform + * @param dir the direction in which to follow the platform + * @param b the state the reservation should be set to + */ +void SetRailStationPlatformReservation(TileIndex start, DiagDirection dir, bool b) +{ + TileIndex tile = start; + TileIndexDiff diff = TileOffsByDiagDir(dir); + + assert(IsRailStationTile(start)); + assert(GetRailStationAxis(start) == DiagDirToAxis(dir)); + + do { + SetRailStationReservation(tile, b); + MarkTileDirtyByTile(tile); + tile = TileAdd(tile, diff); + } while (IsCompatibleTrainStationTile(tile, start)); +} + + +/** + * Set the reservation for a complete depot platform. + * @pre IsExtendedRailDepotTile(start) + * @param start starting tile of the platform + * @param dir the direction in which to follow the platform + * @param b the state the reservation should be set to + */ +void SetRailDepotPlatformReservation(TileIndex start, DiagDirection dir, bool b) +{ + TileIndex tile = start; + TileIndexDiff diff = TileOffsByDiagDir(dir); + + assert(IsExtendedRailDepotTile(start)); + assert(GetRailDepotTrack(start) == DiagDirToDiagTrack(dir)); + + do { + SetDepotReservation(tile, b); + MarkTileDirtyByTile(tile); + tile = TileAdd(tile, diff); + } while (IsCompatibleTrainDepotTile(tile, start)); +} + +/** + * Set the reservation for a complete platform in a given direction. + * @param start starting tile of the platform + * @param dir the direction in which to follow the platform + * @param b the state the reservation should be set to + */ +void SetPlatformReservation(TileIndex start, DiagDirection dir, bool b) +{ + switch (GetPlatformType(start)) { + case PT_RAIL_STATION: + SetRailStationPlatformReservation(start, dir, b); + return; + case PT_RAIL_WAYPOINT: + SetRailStationReservation(start, b); + return; + case PT_RAIL_DEPOT: + SetRailDepotPlatformReservation(start, dir, b); + return; + default: NOT_REACHED(); + } +} + +/** + * Set the reservation for a complete platform. + * @param start A tile of the platform + * @param b the state the reservation should be set to + */ +void SetPlatformReservation(TileIndex start, bool b) +{ + DiagDirection dir; + switch (GetPlatformType(start)) { + case PT_RAIL_STATION: + NOT_REACHED(); + case PT_RAIL_WAYPOINT: + NOT_REACHED(); + case PT_RAIL_DEPOT: + assert(IsExtendedRailDepotTile(start)); + dir = GetRailDepotDirection(start); + SetRailDepotPlatformReservation(start, dir, b); + SetRailDepotPlatformReservation(start, ReverseDiagDir(dir), b); + return; + default: NOT_REACHED(); + } +} + +/** + * Get the length of a rail station platform. + * @pre IsRailStationTile(tile) + * @param tile Tile to check + * @return The length of the platform in tile length. + */ +uint GetRailStationPlatformLength(TileIndex tile) +{ + assert(IsRailStationTile(tile)); + + TileIndexDiff delta = (GetRailStationAxis(tile) == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); + + TileIndex t = tile; + uint len = 0; + do { + t -= delta; + len++; + } while (IsCompatibleTrainStationTile(t, tile)); + + t = tile; + do { + t += delta; + len++; + } while (IsCompatibleTrainStationTile(t, tile)); + + return len - 1; +} + +/** + * Get the length of a rail station platform in a given direction. + * @pre IsRailStationTile(tile) + * @param tile Tile to check + * @param dir Direction to check + * @return The length of the platform in tile length in the given direction. + */ +uint GetRailStationPlatformLength(TileIndex tile, DiagDirection dir) +{ + TileIndex start_tile = tile; + uint length = 0; + assert(IsRailStationTile(tile)); + assert(dir < DIAGDIR_END); + + do { + length++; + tile += TileOffsByDiagDir(dir); + } while (IsCompatibleTrainStationTile(tile, start_tile)); + + return length; +} + +/** + * Get the length of a rail depot platform. + * @pre IsDepotTypeTile(tile, TRANSPORT_RAIL) + * @param tile Tile to check + * @return The length of the platform in tile length. + */ +uint GetRailDepotPlatformLength(TileIndex tile) +{ + assert(IsExtendedRailDepotTile(tile)); + + TileIndexDiff delta = (GetRailDepotTrack(tile) == TRACK_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); + + TileIndex t = tile; + uint len = 0; + do { + t -= delta; + len++; + } while (IsCompatibleTrainDepotTile(t, tile)); + + t = tile; + do { + t += delta; + len++; + } while (IsCompatibleTrainDepotTile(t, tile)); + + return len - 1; +} + +/** + * Get the length of a road depot platform. + * @pre IsDepotTypeTile(tile, TRANSPORT_ROAD) + * @param tile Tile to check + * @param rtt Whether to check for road or tram type. + * @return The length of the platform in tile length. + */ +uint GetRoadDepotPlatformLength(TileIndex tile, RoadTramType rtt) +{ + assert(IsExtendedRoadDepotTile(tile)); + + DiagDirection dir = GetRoadDepotDirection(tile); + TileIndexDiff delta = TileOffsByDiagDir(dir); + + TileIndex t = tile; + uint len = 0; + do { + len++; + if ((GetRoadBits(t, rtt) & DiagDirToRoadBits(dir)) == ROAD_NONE) break; + t -= delta; + } while (IsCompatibleRoadDepotTile(t, tile, rtt)); + + t = tile; + dir = ReverseDiagDir(dir); + do { + len++; + if ((GetRoadBits(t, rtt) & DiagDirToRoadBits(dir)) == ROAD_NONE) break; + t += delta; + } while (IsCompatibleRoadDepotTile(t, tile, rtt)); + + return len - 1; +} + + +/** + * Get the length of a rail depot platform in a given direction. + * @pre IsRailDepotTile(tile) + * @param tile Tile to check + * @param dir Direction to check + * @return The length of the platform in tile length in the given direction. + */ +uint GetRailDepotPlatformLength(TileIndex tile, DiagDirection dir) +{ + TileIndex start_tile = tile; + uint length = 0; + assert(IsExtendedRailDepotTile(tile)); + assert(dir < DIAGDIR_END); + + do { + length++; + tile += TileOffsByDiagDir(dir); + } while (IsCompatibleTrainDepotTile(tile, start_tile)); + + return length; +} + +/** + * Get the length of a road depot platform in a given direction. + * @pre IsRoadDepotTile(tile) + * @param tile Tile to check + * @param dir Direction to check + * @param rtt Whether to check for road or tram type. + * @return The length of the platform in tile length in the given direction. + */ +uint GetRoadDepotPlatformLength(TileIndex tile, DiagDirection dir, RoadTramType rtt) +{ + TileIndex start_tile = tile; + uint length = 0; + assert(IsExtendedRoadDepotTile(tile)); + assert(dir < DIAGDIR_END); + + do { + length++; + if ((GetRoadBits(tile, rtt) & DiagDirToRoadBits(dir)) == ROAD_NONE) break; + tile += TileOffsByDiagDir(dir); + } while (IsCompatibleRoadDepotTile(tile, start_tile, rtt)); + + return length; +} + +/** + * Get the length of a platform. + * @param tile Tile to check + * @param rtt Whether to check for road or tram type (only for road transport). + * @return The length of the platform in tile length. + */ +uint GetPlatformLength(TileIndex tile, RoadTramType rtt) +{ + switch (GetPlatformType(tile)) { + case PT_RAIL_STATION: + return GetRailStationPlatformLength(tile); + case PT_RAIL_WAYPOINT: + return 1; + case PT_RAIL_DEPOT: + return GetRailDepotPlatformLength(tile); + case PT_ROAD_DEPOT: + return GetRoadDepotPlatformLength(tile, rtt); + default: NOT_REACHED(); + } +} + +/** + * Get the length of a rail depot platform in a given direction. + * @pre IsRailDepotTile(tile) + * @param tile Tile to check + * @param dir Direction to check + * @param rtt Whether to check for road or tram type (only for road transport). + * @return The length of the platform in tile length in the given direction. + */ +uint GetPlatformLength(TileIndex tile, DiagDirection dir, RoadTramType rtt) +{ + switch (GetPlatformType(tile)) { + case PT_RAIL_STATION: + return GetRailStationPlatformLength(tile, dir); + case PT_RAIL_WAYPOINT: + return 1; + case PT_RAIL_DEPOT: + return GetRailDepotPlatformLength(tile, dir); + case PT_ROAD_DEPOT: + return GetRoadDepotPlatformLength(tile, dir, rtt); + default: NOT_REACHED(); + } +} + +/** + * Get a tile where a rail station platform begins or ends. + * @pre IsRailStationTile(tile) + * @param tile Tile to check + * @param dir The diagonal direction to check + * @return The last tile of the platform seen from tile with direction dir. + */ +TileIndex GetRailStationExtreme(TileIndex tile, DiagDirection dir) +{ + assert(IsRailStationTile(tile)); + assert(GetRailStationAxis(tile) == DiagDirToAxis(dir)); + TileIndexDiff delta = TileOffsByDiagDir(dir); + + TileIndex t = tile; + do { + t -= delta; + } while (IsCompatibleTrainStationTile(t, tile)); + + return t + delta; +} + +/** + * Get a tile where a depot platform begins or ends. + * @pre IsExtendedDepotTile(tile) + * @param tile Tile to check + * @param dir The diagonal direction to check + * @return The last tile of the platform seen from tile with direction dir. + */ +TileIndex GetRailDepotExtreme(TileIndex tile, DiagDirection dir) +{ + assert(IsExtendedDepotTile(tile)); + assert(GetRailDepotTrack(tile) == DiagDirToDiagTrack(dir)); + TileIndexDiff delta = TileOffsByDiagDir(dir); + + TileIndex t = tile; + do { + t -= delta; + } while (IsCompatibleTrainDepotTile(t, tile)); + + return t + delta; +} + +/** + * Get a tile where a platform begins or ends. + * @param tile Tile to check + * @param dir Direction to check + * @return The last tile of the platform seen from tile with direction dir. + */ +TileIndex GetPlatformExtremeTile(TileIndex tile, DiagDirection dir) +{ + switch (GetPlatformType(tile)) { + case PT_RAIL_STATION: + return GetRailStationExtreme(tile, dir); + case PT_RAIL_WAYPOINT: + return tile; + case PT_RAIL_DEPOT: + return GetRailDepotExtreme(tile, dir); + default: NOT_REACHED(); + } +} + +/** + * Get the tiles belonging to a platform. + * @param tile Tile of a platform + * @return the tile area of the platform + */ +TileArea GetPlatformTileArea(TileIndex tile) +{ + switch (GetPlatformType(tile)) { + case PT_RAIL_STATION: { + assert(IsRailStationTile(tile)); + DiagDirection dir = AxisToDiagDir(GetRailStationAxis(tile)); + return TileArea(GetRailStationExtreme(tile, dir), GetRailStationExtreme(tile, ReverseDiagDir(dir))); + } + case PT_RAIL_WAYPOINT: + return TileArea(tile); + case PT_RAIL_DEPOT: { + assert(IsExtendedRailDepotTile(tile)); + DiagDirection dir = GetRailDepotDirection(tile); + return TileArea(GetRailDepotExtreme(tile, dir), GetRailDepotExtreme(tile, ReverseDiagDir(dir))); + } + default: NOT_REACHED(); + } +} + + +/** + * Check whether this tile is an extreme of a platform. + * @param tile Tile to check + * @return Whether the tile is the extreme of a platform. + */ +bool IsAnyStartPlatformTile(TileIndex tile) +{ + assert(IsExtendedRailDepotTile(tile)); + DiagDirection dir = GetRailDepotDirection(tile); + return tile == GetPlatformExtremeTile(tile, dir) || tile == GetPlatformExtremeTile(tile, ReverseDiagDir(dir)); +} diff --git a/src/platform_func.h b/src/platform_func.h new file mode 100644 index 0000000000..baa5912874 --- /dev/null +++ b/src/platform_func.h @@ -0,0 +1,171 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file platform_func.h Functions related with platforms (tiles in a row that are connected somehow). */ + +#ifndef PLATFORM_FUNC_H +#define PLATFORM_FUNC_H + +#include "station_map.h" +#include "depot_map.h" +#include "platform_type.h" +#include "road_map.h" + +/** + * Check if a tile is a valid continuation to a railstation tile. + * The tile \a test_tile is a valid continuation to \a station_tile, if all of the following are true: + * \li \a test_tile is a rail station tile + * \li the railtype of \a test_tile is compatible with the railtype of \a station_tile + * \li the tracks on \a test_tile and \a station_tile are in the same direction + * \li both tiles belong to the same station + * \li \a test_tile is not blocked (@see IsStationTileBlocked) + * @param test_tile Tile to test + * @param station_tile Station tile to compare with + * @pre IsRailStationTile(station_tile) + * @return true if the two tiles are compatible + */ +static inline bool IsCompatibleTrainStationTile(TileIndex test_tile, TileIndex station_tile) +{ + assert(IsRailStationTile(station_tile)); + return IsRailStationTile(test_tile) && !IsStationTileBlocked(test_tile) && + IsCompatibleRail(GetRailType(test_tile), GetRailType(station_tile)) && + GetRailStationAxis(test_tile) == GetRailStationAxis(station_tile) && + GetStationIndex(test_tile) == GetStationIndex(station_tile); +} + +/** + * Check if a tile is a valid continuation to an extended rail depot tile. + * The tile \a test_tile is a valid continuation to \a depot_tile, if all of the following are true: + * \li \a test_tile is an extended depot tile + * \li \a test_tile and \a depot_tile have the same rail type + * \li the tracks on \a test_tile and \a depot_tile are in the same direction + * \li both tiles belong to the same depot + * @param test_tile Tile to test + * @param depot_tile Depot tile to compare with + * @pre IsExtendedRailDepotTile(depot_tile) + * @return true if the two tiles are compatible + */ +static inline bool IsCompatibleTrainDepotTile(TileIndex test_tile, TileIndex depot_tile) +{ + assert(IsExtendedRailDepotTile(depot_tile)); + return IsExtendedRailDepotTile(test_tile) && + GetRailType(test_tile) == GetRailType(depot_tile) && + GetRailDepotTrack(test_tile) == GetRailDepotTrack(depot_tile) && + GetDepotIndex(test_tile) == GetDepotIndex(depot_tile); +} + +/** + * Check if a tile is a valid continuation to an extended road depot tile. + * The tile \a test_tile is a valid continuation to \a depot_tile, if all of the following are true: + * \li \a test_tile is an extended depot tile + * \li \a test_tile and \a depot_tile have the same road type and appropriate road bits + * \li the tracks on \a test_tile and \a depot_tile are in the same direction + * \li both tiles belong to the same depot + * @param test_tile Tile to test + * @param depot_tile Depot tile to compare with + * @param rtt Whether road or tram type. + * @pre IsExtendedRoadDepotTile(depot_tile) + * @return true if the two tiles are compatible + */ +static inline bool IsCompatibleRoadDepotTile(TileIndex test_tile, TileIndex depot_tile, RoadTramType rtt) +{ + assert(IsExtendedRoadDepotTile(depot_tile)); + if (!IsExtendedRoadDepotTile(test_tile)) return false; + if (GetDepotIndex(test_tile) != GetDepotIndex(depot_tile)) return false; + if (GetRoadType(depot_tile, rtt) != GetRoadType(test_tile, rtt)) return false; + + DiagDirection dir = DiagdirBetweenTiles(test_tile, depot_tile); + assert(dir != INVALID_DIAGDIR); + return (GetRoadBits(test_tile, rtt) & DiagDirToRoadBits(dir)) != ROAD_NONE; +} + +/** + * Returns the type of platform of a given tile. + * @param tile Tile to check + * @return the type of platform (rail station, rail waypoint...) + */ +static inline PlatformType GetPlatformType(TileIndex tile) +{ + switch (GetTileType(tile)) { + case MP_STATION: + if (IsRailStation(tile)) return PT_RAIL_STATION; + if (IsRailWaypoint(tile)) return PT_RAIL_WAYPOINT; + break; + case MP_RAILWAY: + if (IsExtendedRailDepotTile(tile)) return PT_RAIL_DEPOT; + break; + case MP_ROAD: + if (IsExtendedRoadDepotTile(tile)) return PT_ROAD_DEPOT; + break; + default: break; + } + + return INVALID_PLATFORM_TYPE; +} + +/** + * Check whether a tile is a known platform type. + * @param tile to check + * @return whether the tile is a known platform type. + */ +static inline bool IsPlatformTile(TileIndex tile) +{ + return GetPlatformType(tile) != INVALID_PLATFORM_TYPE; +} + +/** + * Check whether a platform tile is reserved. + * @param tile to check + * @return whether the platform tile is reserved + */ +static inline bool HasPlatformReservation(TileIndex tile) +{ + switch(GetPlatformType(tile)) { + case PT_RAIL_STATION: + case PT_RAIL_WAYPOINT: + return HasStationReservation(tile); + case PT_RAIL_DEPOT: + return HasDepotReservation(tile); + default: NOT_REACHED(); + } +} + +/** + * Check whether two tiles are compatible platform tiles: they must have the same + * platform type and (depending on the platform type) its railtype or other specs. + * @param test_tile the tile to check + * @param orig_tile the tile with the platform type we are interested in + * @param rtt Whether to check road or tram types (only for road transport); + * @return whether the two tiles are compatible tiles for defining a platform + */ +static inline bool IsCompatiblePlatformTile(TileIndex test_tile, TileIndex orig_tile, RoadTramType rtt = RTT_ROAD) +{ + switch (GetPlatformType(orig_tile)) { + case PT_RAIL_STATION: + return IsCompatibleTrainStationTile(test_tile, orig_tile); + case PT_RAIL_WAYPOINT: + return test_tile == orig_tile; + case PT_RAIL_DEPOT: + return IsCompatibleTrainDepotTile(test_tile, orig_tile); + case PT_ROAD_DEPOT: + return IsCompatibleRoadDepotTile(test_tile, orig_tile, rtt); + default: NOT_REACHED(); + } +} + +void SetPlatformReservation(TileIndex start, DiagDirection dir, bool b); +void SetPlatformReservation(TileIndex start, bool b); + +uint GetPlatformLength(TileIndex tile, RoadTramType rtt = RTT_ROAD); +uint GetPlatformLength(TileIndex tile, DiagDirection dir, RoadTramType rtt = RTT_ROAD); + +TileIndex GetPlatformExtremeTile(TileIndex tile, DiagDirection dir); +TileArea GetPlatformTileArea(TileIndex tile); + +bool IsAnyStartPlatformTile(TileIndex tile); + +#endif /* PLATFORM_FUNC_H */ diff --git a/src/platform_type.h b/src/platform_type.h new file mode 100644 index 0000000000..e6c543465a --- /dev/null +++ b/src/platform_type.h @@ -0,0 +1,24 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file platform_type.h Types related to platforms. */ + +#ifndef PLATFORM_TYPE_H +#define PLATFORM_TYPE_H + +#include "core/enum_type.hpp" + +enum PlatformType { + PT_RAIL_STATION, + PT_RAIL_WAYPOINT, + PT_RAIL_DEPOT, + PT_ROAD_DEPOT, + PT_END, + INVALID_PLATFORM_TYPE = PT_END, +}; + +#endif /* PLATFORM_TYPE_H */ 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..a759ee8236 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -33,6 +33,7 @@ #include "object_map.h" #include "rail_cmd.h" #include "landscape_cmd.h" +#include "platform_func.h" #include "table/strings.h" #include "table/railtypes.h" @@ -952,81 +953,163 @@ 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 extended build extended 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, bool extended, 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); + if (Company::IsValidHumanID(_current_company) && !HasBit(_settings_game.depot.rail_depot_types, extended)) return_cmd_error(STR_ERROR_DEPOT_TYPE_NOT_AVAILABLE); 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; + + Axis axis = DiagDirToAxis(dir); + /* Do not allow extending already occupied platforms. */ + if (extended && join_to != NEW_DEPOT) { + TileArea ta_ext = TileArea(ta.tile, ta.w, ta.h).Expand(1); + + uint max_coord; + uint min_coord; + if (axis == AXIS_X) { + min_coord = TileY(ta.tile); + max_coord = min_coord + ta.h; + } else { + min_coord = TileX(ta.tile); + max_coord = min_coord + ta.w; + } + + for (Tile t : ta_ext) { + if (!IsExtendedRailDepotTile(t)) continue; + if (GetDepotIndex(t) != depot->index) continue; + if (GetRailType(t) != railtype) continue; + if (!HasDepotReservation(t)) continue; + if (DiagDirToAxis(GetRailDepotDirection(t)) != axis) continue; + uint current = (axis == AXIS_X) ? TileY(t) : TileX(t); + if (!IsInsideMM(current, min_coord, max_coord)) continue; + return_cmd_error(STR_ERROR_DEPOT_EXTENDING_PLATFORMS); + } + } + + uint8_t num_new_depot_tiles = 0; + uint8_t num_overbuilt_depot_tiles = 0; /* Prohibit construction if * The tile is non-flat AND * 1) build-on-slopes is disabled * 2) the tile is steep i.e. spans two height levels * 3) the exit points in the wrong direction + * 4) the tile is not an already built depot (or it is a compatible single rail tile for building extended depots) */ + 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); + } + if (extended && !CanBuildDepotByTileh(ReverseDiagDir(dir), tileh)) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } + cost.AddCost(_price[PR_BUILD_FOUNDATION]); } - 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 (extended) { + if (IsPlainRailTile(t) && !HasSignals(t) && GetRailType(t) == railtype) { + /* Allow overbuilding if the tile: + * - has rail, but no signals + * - it has exactly one track + * - the track is in line with the depot + * - the current rail type is the same as the to-be-built + */ + TrackBits tracks = GetTrackBits(t); + Track track = RemoveFirstTrack(&tracks); + uint invalid_dirs = 5 << DiagDirToAxis(dir); + Track expected_track = HasBit(invalid_dirs, DIAGDIR_NE) ? TRACK_X : TRACK_Y; - if (dir == GetRailDepotDirection(tile)) return CommandCost(); + if (tracks == TRACK_BIT_NONE && track == expected_track) { + cost.AddCost(Command::Do(flags, t, track).GetCost()); + /* With flags & ~DC_EXEC CmdLandscapeClear would fail since the rail still exists */ + if (cost.Failed()) return cost; + goto new_depot_tile; + } + } - ret = EnsureNoVehicleOnGround(tile); - if (ret.Failed()) return ret; + /* Skip already existing and compatible extended depots. */ + if (IsRailDepotTile(t) && IsExtendedRailDepotTile(t) && + GetDepotIndex(t) == join_to && railtype == GetRailType(t)) { + if (axis == DiagDirToAxis(GetRailDepotDirection(t))) continue; + } + } else { + /* Check whether this is a standard depot tile and it needs to be rotated. */ + if (IsRailDepotTile(t) && IsStandardRailDepotTile(t) && + GetDepotIndex(t) == join_to && railtype == GetRailType(t)) { + if (dir == GetRailDepotDirection(t)) continue; - rotate_existing_depot = true; - } + ret = EnsureNoVehicleOnGround(t); + if (ret.Failed()) return ret; - if (!rotate_existing_depot) { - cost.AddCost(Command::Do(flags, tile)); + num_overbuilt_depot_tiles++; + if (flags & DC_EXEC) { + SetRailDepotExitDirection(t, dir); + AddSideToSignalBuffer(t, INVALID_DIAGDIR, _current_company); + YapfNotifyTrackLayoutChange(t, DiagDirToDiagTrack(dir)); + MarkTileDirtyByTile(t); + } + continue; + } + } + + cost.AddCost(Command::Do(flags, t)); if (cost.Failed()) return cost; - if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); +new_depot_tile: + num_new_depot_tiles++; - if (!Depot::CanAllocateItem()) return CMD_ERROR; + if (flags & DC_EXEC) { + MakeRailDepot(t, _current_company, depot->index, dir, railtype); + SB(t.m5(), 5, 1, extended); + + if (extended) { + AddTrackToSignalBuffer(t, DiagDirToDiagTrack(dir), _current_company); + } else { + AddSideToSignalBuffer(t, INVALID_DIAGDIR, _current_company); + } + YapfNotifyTrackLayoutChange(t, DiagDirToDiagTrack(dir)); + MarkTileDirtyByTile(t); + } } + if (num_new_depot_tiles + num_overbuilt_depot_tiles == 0) return CommandCost(); + + cost.AddCost(_price[PR_BUILD_DEPOT_TRAIN] * (num_new_depot_tiles + num_overbuilt_depot_tiles)); + cost.AddCost(RailBuildCost(railtype) * (num_new_depot_tiles + num_overbuilt_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; } @@ -1540,6 +1623,56 @@ static Vehicle *UpdateTrainPowerProc(Vehicle *v, void *data) return nullptr; } +/** + * Returns whether a depot has an extended depot + * tile which is reserved. + * @param Depot pointer to a depot + * @return true iff \a dep has an extended depot tile reserved. + */ +bool HasAnyExtendedDepotReservedTile(Depot *dep) +{ + assert(dep != nullptr); + for (TileIndex tile : dep->ta) { + if (!IsExtendedDepotTile(tile)) continue; + if (GetDepotIndex(tile) != dep->index) continue; + if (HasDepotReservation(tile)) return true; + } + + return false; +} + +CommandCost ConvertExtendedDepot(DoCommandFlag flags, Depot *dep, RailType rail_type) +{ + CommandCost cost(EXPENSES_CONSTRUCTION); + assert(dep->owner == _current_company); + Company *c = Company::Get(dep->owner); + + for (TileIndex tile : dep->ta) { + if (!IsDepotTile(tile)) continue; + if (GetDepotIndex(tile) != dep->index) continue; + assert(!HasDepotReservation(tile)); + assert(dep->owner == GetTileOwner(tile)); + + /* Original railtype we are converting from */ + RailType type = GetRailType(tile); + + if (type == rail_type || (_settings_game.vehicle.disable_elrails && rail_type == RAILTYPE_RAIL && type == RAILTYPE_ELECTRIC)) continue; + + cost.AddCost(RailConvertCost(type, rail_type)); + + if (flags & DC_EXEC) { + c->infrastructure.rail[type]--; + c->infrastructure.rail[rail_type]++; + SetRailType(tile, rail_type); + MarkTileDirtyByTile(tile); + YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile)); + DirtyCompanyInfrastructureWindows(c->index); + } + } + + return cost; +} + /** * Convert one rail type to the other. You can convert normal rail to * monorail/maglev easily or vice-versa. @@ -1558,6 +1691,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. @@ -1647,31 +1781,27 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s switch (tt) { case MP_RAILWAY: - switch (GetRailTileType(tile)) { - case RAIL_TILE_DEPOT: - if (flags & DC_EXEC) { - /* notify YAPF about the track layout change */ - YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile)); + found_convertible_track = true; + if (GetRailTileType(tile) == RAIL_TILE_DEPOT) { + if (flags & DC_EXEC) { + /* notify YAPF about the track layout change */ + YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile)); + } - /* Update build vehicle window related to this depot */ - InvalidateWindowData(WC_VEHICLE_DEPOT, tile); - InvalidateWindowData(WC_BUILD_VEHICLE, tile); - } - found_convertible_track = true; - cost.AddCost(RailConvertCost(type, totype)); - break; + if (find(affected_depots.begin(), affected_depots.end(), (tile)) == affected_depots.end()) { + affected_depots.push_back(GetDepotIndex(tile)); + } - default: // RAIL_TILE_NORMAL, RAIL_TILE_SIGNALS - if (flags & DC_EXEC) { - /* notify YAPF about the track layout change */ - TrackBits tracks = GetTrackBits(tile); - while (tracks != TRACK_BIT_NONE) { - YapfNotifyTrackLayoutChange(tile, RemoveFirstTrack(&tracks)); - } + cost.AddCost(RailConvertCost(type, totype)); + } else { // RAIL_TILE_NORMAL, RAIL_TILE_SIGNALS + if (flags & DC_EXEC) { + /* notify YAPF about the track layout change */ + TrackBits tracks = GetTrackBits(tile); + while (tracks != TRACK_BIT_NONE) { + YapfNotifyTrackLayoutChange(tile, RemoveFirstTrack(&tracks)); } - found_convertible_track = true; - cost.AddCost(RailConvertCost(type, totype) * CountBits(GetTrackBits(tile))); - break; + } + cost.AddCost(RailConvertCost(type, totype) * CountBits(GetTrackBits(tile))); } break; @@ -1753,6 +1883,17 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s } } + /* Update affected depots. */ + for (auto &depot_tile : affected_depots) { + Depot *dep = Depot::Get(depot_tile); + if (HasAnyExtendedDepotReservedTile(dep)) cost.MakeError(STR_ERROR_DEPOT_EXTENDED_RAIL_DEPOT_IS_NOT_FREE); + + if (flags & DC_EXEC) { + dep->RescanDepotTiles(); + InvalidateWindowData(WC_VEHICLE_DEPOT, dep->index); + } + } + if (flags & DC_EXEC) { /* Railtype changed, update trains as when entering different track */ for (Train *v : affected_trains) { @@ -1763,8 +1904,10 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s return found_convertible_track ? cost : error; } -static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) +static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags, bool keep_rail) { + assert(IsRailDepotTile(tile)); + if (_current_company != OWNER_WATER) { CommandCost ret = CheckTileOwnership(tile); if (ret.Failed()) return ret; @@ -1773,10 +1916,21 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) CommandCost ret = EnsureNoVehicleOnGround(tile); if (ret.Failed()) return ret; + if (HasDepotReservation(tile)) return CMD_ERROR; + + CommandCost total_cost(EXPENSES_CONSTRUCTION); + + if (keep_rail) { + /* Don't refund the 'steel' of the track when we keep the rail. */ + total_cost.AddCost(-_price[PR_CLEAR_RAIL]); + } + 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,17 +1938,57 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) if (v != nullptr) FreeTrainTrackReservation(v); } - Company::Get(owner)->infrastructure.rail[GetRailType(tile)]--; - DirtyCompanyInfrastructureWindows(owner); + Track track = GetRailDepotTrack(tile); + RailType rt = GetRailType(tile); + bool is_extended_depot = IsExtendedDepot(tile); - delete Depot::GetByTile(tile); DoClearSquare(tile); - AddSideToSignalBuffer(tile, dir, owner); + + if (keep_rail) { + MakeRailNormal(tile, depot->owner, TrackToTrackBits(track), rt); + } else { + c->infrastructure.rail[GetRailType(tile)]--; + DirtyCompanyInfrastructureWindows(c->index); + } + + if (is_extended_depot) { + AddTrackToSignalBuffer(tile, DiagDirToDiagTrack(dir), c->index); + } else { + 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]); + total_cost.AddCost(_price[PR_CLEAR_DEPOT_TRAIN]); + return total_cost; +} + +/** + * 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, IsExtendedDepot(t)); + if (ret.Failed()) return ret; + cost.AddCost(ret); + } + + return cost; } static CommandCost ClearTile_Track(TileIndex tile, DoCommandFlag flags) @@ -1845,7 +2039,7 @@ static CommandCost ClearTile_Track(TileIndex tile, DoCommandFlag flags) } case RAIL_TILE_DEPOT: - return RemoveTrainDepot(tile, flags); + return RemoveTrainDepot(tile, flags, false); default: return CMD_ERROR; @@ -2072,7 +2266,12 @@ static inline void DrawTrackSprite(SpriteID sprite, PaletteID pal, const TileInf static void DrawTrackBitsOverlay(TileInfo *ti, TrackBits track, const RailTypeInfo *rti) { RailGroundType rgt = GetRailGroundType(ti->tile); - Foundation f = GetRailFoundation(ti->tileh, track); + Foundation f = FOUNDATION_NONE; + if (IsRailDepot(ti->tile)) { + if (ti->tileh != SLOPE_FLAT) f = FOUNDATION_LEVELED; + } else { + f = GetRailFoundation(ti->tileh, track); + } Corner halftile_corner = CORNER_INVALID; if (IsNonContinuousFoundation(f)) { @@ -2112,7 +2311,18 @@ static void DrawTrackBitsOverlay(TileInfo *ti, TrackBits track, const RailTypeIn bool no_combine = ti->tileh == SLOPE_FLAT && HasBit(rti->flags, RTF_NO_SPRITE_COMBINE); SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY); SpriteID ground = GetCustomRailSprite(rti, ti->tile, no_combine ? RTSG_GROUND_COMPLETE : RTSG_GROUND); - TrackBits pbs = _settings_client.gui.show_track_reservation ? GetRailReservationTrackBits(ti->tile) : TRACK_BIT_NONE; + + TrackBits pbs = TRACK_BIT_NONE; + if (_settings_client.gui.show_track_reservation) { + if (IsPlainRail(ti->tile)) { + pbs = GetRailReservationTrackBits(ti->tile); + } else { + assert(IsRailDepot(ti->tile)); + if (HasDepotReservation(ti->tile)) { + pbs = track; + } + } + } if (track == TRACK_BIT_NONE) { /* Half-tile foundation, no track here? */ @@ -2342,7 +2552,14 @@ static void DrawTrackBits(TileInfo *ti, TrackBits track) /* PBS debugging, draw reserved tracks darker */ if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation) { /* Get reservation, but mask track on halftile slope */ - TrackBits pbs = GetRailReservationTrackBits(ti->tile) & track; + TrackBits pbs = TRACK_BIT_NONE; + if (IsPlainRail(ti->tile)) { + pbs = GetRailReservationTrackBits(ti->tile) & track; + } else { + assert(IsRailDepot(ti->tile)); + if (HasDepotReservation(ti->tile)) pbs = track; + } + if (pbs & TRACK_BIT_X) { if (ti->tileh == SLOPE_FLAT || ti->tileh == SLOPE_ELEVATED) { DrawGroundSprite(rti->base_sprites.single_x, PALETTE_CRASH); @@ -2425,124 +2642,40 @@ static void DrawTile_Track(TileInfo *ti) _drawtile_track_palette = COMPANY_SPRITE_COLOUR(GetTileOwner(ti->tile)); + TrackBits rails = TRACK_BIT_NONE; if (IsPlainRail(ti->tile)) { - TrackBits rails = GetTrackBits(ti->tile); - - DrawTrackBits(ti, rails); - - if (HasBit(_display_opt, DO_FULL_DETAIL)) DrawTrackDetails(ti, rti); - - if (HasRailCatenaryDrawn(GetRailType(ti->tile))) DrawRailCatenary(ti); - - if (HasSignals(ti->tile)) DrawSignals(ti->tile, rails, rti); + rails = GetTrackBits(ti->tile); } else { + assert(IsRailDepot(ti->tile)); + DiagDirection dir = GetRailDepotDirection(ti->tile); + if (IsDiagDirFacingSouth(dir) || IsTransparencySet(TO_BUILDINGS)) { + rails = TrackToTrackBits(GetRailDepotTrack(ti->tile)); + } + } + + DrawTrackBits(ti, rails); + + if (IsPlainRail(ti->tile) && HasBit(_display_opt, DO_FULL_DETAIL)) DrawTrackDetails(ti, rti); + + if (HasRailCatenaryDrawn(GetRailType(ti->tile))) DrawRailCatenary(ti); + + if (IsRailDepot(ti->tile) && !IsInvisibilitySet(TO_BUILDINGS)) { /* draw depot */ - const DrawTileSprites *dts; - PaletteID pal = PAL_NONE; - SpriteID relocation; - - if (ti->tileh != SLOPE_FLAT) DrawFoundation(ti, FOUNDATION_LEVELED); - - if (IsInvisibilitySet(TO_BUILDINGS)) { - /* Draw rail instead of depot */ - dts = &_depot_invisible_gfx_table[GetRailDepotDirection(ti->tile)]; - } else { - dts = &_depot_gfx_table[GetRailDepotDirection(ti->tile)]; - } - - SpriteID image; - if (rti->UsesOverlay()) { - image = SPR_FLAT_GRASS_TILE; - } else { - image = dts->ground.sprite; - if (image != SPR_FLAT_GRASS_TILE) image += rti->GetRailtypeSpriteOffset(); - } - - /* Adjust ground tile for desert and snow. */ - if (IsSnowRailGround(ti->tile)) { - if (image != SPR_FLAT_GRASS_TILE) { - image += rti->snow_offset; // tile with tracks - } else { - image = SPR_FLAT_SNOW_DESERT_TILE; // flat ground - } - } - - DrawGroundSprite(image, GroundSpritePaletteTransform(image, pal, _drawtile_track_palette)); - - if (rti->UsesOverlay()) { - SpriteID ground = GetCustomRailSprite(rti, ti->tile, RTSG_GROUND); - - switch (GetRailDepotDirection(ti->tile)) { - case DIAGDIR_NE: - if (!IsInvisibilitySet(TO_BUILDINGS)) break; - [[fallthrough]]; - case DIAGDIR_SW: - DrawGroundSprite(ground + RTO_X, PAL_NONE); - break; - case DIAGDIR_NW: - if (!IsInvisibilitySet(TO_BUILDINGS)) break; - [[fallthrough]]; - case DIAGDIR_SE: - DrawGroundSprite(ground + RTO_Y, PAL_NONE); - break; - default: - break; - } - - if (_settings_client.gui.show_track_reservation && HasDepotReservation(ti->tile)) { - SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY); - - switch (GetRailDepotDirection(ti->tile)) { - case DIAGDIR_NE: - if (!IsInvisibilitySet(TO_BUILDINGS)) break; - [[fallthrough]]; - case DIAGDIR_SW: - DrawGroundSprite(overlay + RTO_X, PALETTE_CRASH); - break; - case DIAGDIR_NW: - if (!IsInvisibilitySet(TO_BUILDINGS)) break; - [[fallthrough]]; - case DIAGDIR_SE: - DrawGroundSprite(overlay + RTO_Y, PALETTE_CRASH); - break; - default: - break; - } - } - } else { - /* PBS debugging, draw reserved tracks darker */ - if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && HasDepotReservation(ti->tile)) { - switch (GetRailDepotDirection(ti->tile)) { - case DIAGDIR_NE: - if (!IsInvisibilitySet(TO_BUILDINGS)) break; - [[fallthrough]]; - case DIAGDIR_SW: - DrawGroundSprite(rti->base_sprites.single_x, PALETTE_CRASH); - break; - case DIAGDIR_NW: - if (!IsInvisibilitySet(TO_BUILDINGS)) break; - [[fallthrough]]; - case DIAGDIR_SE: - DrawGroundSprite(rti->base_sprites.single_y, PALETTE_CRASH); - break; - default: - break; - } - } - } + const DrawTileSprites *dts = &_depot_gfx_table[GetRailDepotDirection(ti->tile)]; int depot_sprite = GetCustomRailSprite(rti, ti->tile, RTSG_DEPOT); - relocation = depot_sprite != 0 ? depot_sprite - SPR_RAIL_DEPOT_SE_1 : rti->GetRailtypeSpriteOffset(); - - if (HasRailCatenaryDrawn(GetRailType(ti->tile))) DrawRailCatenary(ti); + SpriteID relocation = depot_sprite != 0 ? depot_sprite - SPR_RAIL_DEPOT_SE_1 : rti->GetRailtypeSpriteOffset(); DrawRailTileSeq(ti, dts, TO_BUILDINGS, relocation, 0, _drawtile_track_palette); } + + if (HasSignals(ti->tile)) DrawSignals(ti->tile, rails, rti); + DrawBridgeMiddle(ti); } void DrawTrainDepotSprite(int x, int y, int dir, RailType railtype) { - const DrawTileSprites *dts = &_depot_gfx_table[dir]; + const DrawTileSprites *dts = &_depot_gfx_gui_table[dir]; const RailTypeInfo *rti = GetRailTypeInfo(railtype); SpriteID image = rti->UsesOverlay() ? SPR_FLAT_GRASS_TILE : dts->ground.sprite; uint32_t offset = rti->GetRailtypeSpriteOffset(); @@ -2758,6 +2891,13 @@ static TrackStatus GetTileTrackStatus_Track(TileIndex tile, TransportType mode, } case RAIL_TILE_DEPOT: { + if (IsExtendedRailDepot(tile)) { + Track track = GetRailDepotTrack(tile); + trackbits = TrackToTrackBits(track); + break; + } + + /* Small depot. */ DiagDirection dir = GetRailDepotDirection(tile); if (side != INVALID_DIAGDIR && side != dir) break; @@ -2774,7 +2914,7 @@ static bool ClickTile_Track(TileIndex tile) { if (!IsRailDepot(tile)) return false; - ShowDepotWindow(tile, VEH_TRAIN); + ShowDepotWindow(GetDepotIndex(tile)); return true; } @@ -2855,7 +2995,7 @@ static void GetTileDesc_Track(TileIndex tile, TileDesc *td) } case RAIL_TILE_DEPOT: - td->str = STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT; + td->str = IsExtendedDepot(tile) ? STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT_EXTENDED : STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT; if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL) { if (td->rail_speed > 0) { td->rail_speed = std::min(td->rail_speed, 61); @@ -2915,6 +3055,7 @@ static const int8_t _deltacoord_leaveoffset[8] = { */ int TicksToLeaveDepot(const Train *v) { + assert(IsStandardRailDepotTile(v->tile)); DiagDirection dir = GetRailDepotDirection(v->tile); int length = v->CalcNextVehicleOffset(); @@ -2936,6 +3077,38 @@ static VehicleEnterTileStatus VehicleEnter_Track(Vehicle *u, TileIndex tile, int /* This routine applies only to trains in depot tiles. */ if (u->type != VEH_TRAIN || !IsRailDepotTile(tile)) return VETSB_CONTINUE; + Train *v = Train::From(u); + + if (IsExtendedRailDepot(tile)) { + DepotID depot_id = GetDepotIndex(tile); + if (!v->current_order.ShouldStopAtDepot(depot_id)) return VETSB_CONTINUE; + + /* Stop position on platform is half the front vehicle length of the train. */ + int stop_pos = v->gcache.cached_veh_length / 2; + + int depot_ahead = (GetPlatformLength(tile, DirToDiagDir(v->direction)) - 1) * TILE_SIZE; + if (depot_ahead > stop_pos) return VETSB_CONTINUE; + + DiagDirection dir = DirToDiagDir(v->direction); + + x &= 0xF; + y &= 0xF; + + if (DiagDirToAxis(dir) != AXIS_X) Swap(x, y); + if (y == TILE_SIZE / 2) { + if (dir == DIAGDIR_SE || dir == DIAGDIR_SW) x = TILE_SIZE - 1 - x; + + if (stop_pos == x) { + return VETSB_ENTERED_DEPOT_PLATFORM; + } else if (stop_pos < x) { + v->vehstatus |= VS_TRAIN_SLOWING; + uint16_t spd = std::max(0, stop_pos * 20 - 15); + if (spd < v->cur_speed) v->cur_speed = spd; + } + } + return VETSB_CONTINUE; + } + /* Depot direction. */ DiagDirection dir = GetRailDepotDirection(tile); @@ -2944,8 +3117,6 @@ static VehicleEnterTileStatus VehicleEnter_Track(Vehicle *u, TileIndex tile, int /* Make sure a train is not entering the tile from behind. */ if (_fractcoords_behind[dir] == fract_coord) return VETSB_CANNOT_ENTER; - Train *v = Train::From(u); - /* Leaving depot? */ if (v->direction == DiagDirToDir(dir)) { /* Calculate the point where the following wagon should be activated. */ @@ -2970,10 +3141,10 @@ static VehicleEnterTileStatus VehicleEnter_Track(Vehicle *u, TileIndex tile, int v->track = TRACK_BIT_DEPOT, v->vehstatus |= VS_HIDDEN; v->direction = ReverseDir(v->direction); - if (v->Next() == nullptr) VehicleEnterDepot(v->First()); + if (v->Next() == nullptr) HandleTrainEnterDepot(v->First()); v->tile = tile; - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); return VETSB_ENTERED_WORMHOLE; } @@ -3080,6 +3251,14 @@ static CommandCost TerraformTile_Track(TileIndex tile, DoCommandFlag flags, int return CommandCost(EXPENSES_CONSTRUCTION, was_water ? _price[PR_CLEAR_WATER] : (Money)0); } else if (_settings_game.construction.build_on_slopes && AutoslopeEnabled() && AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, GetRailDepotDirection(tile))) { + if (IsExtendedRailDepotTile(tile) && GetTileMaxZ(tile) == z_new + GetSlopeMaxZ(tileh_new)) { + DiagDirection direction = GetRailDepotDirection(tile); + if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, direction) || + !AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, ReverseDiagDir(direction))) { + return Command::Do(flags, tile); + } + } + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); } return Command::Do(flags, tile); diff --git a/src/rail_cmd.h b/src/rail_cmd.h index b9f6c0cc03..bcd28fa2e3 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, bool extended, 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, bool extended, DepotID join_to, TileIndex end_tile); #endif /* RAIL_CMD_H */ diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 06819e4c3d..94d67ab5ce 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" @@ -72,7 +73,7 @@ static StationPickerSelection _station_gui; ///< Settings of the station picker. static void HandleStationPlacement(TileIndex start, TileIndex end); -static void ShowBuildTrainDepotPicker(Window *parent); +static void ShowBuildTrainDepotPicker(Window *parent, bool extended_depot); static void ShowBuildWaypointPicker(Window *parent); static Window *ShowStationBuilder(Window *parent); static void ShowSignalBuilder(Window *parent); @@ -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,27 @@ 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, bool extended, DepotID, TileIndex end_tile) { if (result.Failed()) return; + if (extended) 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 +321,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); @@ -334,7 +339,7 @@ static bool RailToolbar_CtrlChanged(Window *w) /* 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->HasWidget(i) && w->IsWidgetLowered(i)) { ToggleRailButton_Remove(w); return true; } @@ -454,6 +459,11 @@ 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->HasWidget(WID_RAT_BUILD_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) || + (this->HasWidget(WID_RAT_BUILD_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_EXTENDED_DEPOT))) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + } + this->Window::Close(); } @@ -505,7 +515,8 @@ struct BuildRailToolbarWindow : Window { this->GetWidget(WID_RAT_BUILD_EW)->widget_data = rti->gui_sprites.build_ew_rail; this->GetWidget(WID_RAT_BUILD_Y)->widget_data = rti->gui_sprites.build_y_rail; this->GetWidget(WID_RAT_AUTORAIL)->widget_data = rti->gui_sprites.auto_rail; - this->GetWidget(WID_RAT_BUILD_DEPOT)->widget_data = rti->gui_sprites.build_depot; + if (this->HasWidget(WID_RAT_BUILD_DEPOT)) this->GetWidget(WID_RAT_BUILD_DEPOT)->widget_data = rti->gui_sprites.build_depot; + if (this->HasWidget(WID_RAT_BUILD_EXTENDED_DEPOT)) this->GetWidget(WID_RAT_BUILD_EXTENDED_DEPOT)->widget_data = rti->gui_sprites.build_depot; this->GetWidget(WID_RAT_CONVERT_RAIL)->widget_data = rti->gui_sprites.convert_rail; this->GetWidget(WID_RAT_BUILD_TUNNEL)->widget_data = rti->gui_sprites.build_tunnel; } @@ -533,6 +544,8 @@ 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_EXTENDED_DEPOT: case WID_RAT_BUILD_WAYPOINT: case WID_RAT_BUILD_STATION: case WID_RAT_BUILD_SIGNALS: @@ -601,8 +614,9 @@ struct BuildRailToolbarWindow : Window { break; case WID_RAT_BUILD_DEPOT: - if (HandlePlacePushButton(this, WID_RAT_BUILD_DEPOT, GetRailTypeInfo(_cur_railtype)->cursor.depot, HT_RECT)) { - ShowBuildTrainDepotPicker(this); + case WID_RAT_BUILD_EXTENDED_DEPOT: + if (HandlePlacePushButton(this, widget, GetRailTypeInfo(_cur_railtype)->cursor.depot, HT_RECT)) { + ShowBuildTrainDepotPicker(this, widget == WID_RAT_BUILD_EXTENDED_DEPOT); this->last_user_action = widget; } break; @@ -689,8 +703,17 @@ struct BuildRailToolbarWindow : Window { 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_EXTENDED_DEPOT: { + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); + + ViewportPlaceMethod vpm = VPM_X_AND_Y_LIMITED; + if (this->last_user_action == WID_RAT_BUILD_DEPOT) { + 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 +809,22 @@ struct BuildRailToolbarWindow : Window { } } break; + + case DDSP_BUILD_DEPOT: + case DDSP_REMOVE_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; + bool extended = this->last_user_action == WID_RAT_BUILD_EXTENDED_DEPOT; + + 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, extended, join_to, end_tile); + }; + + ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_TRAIN); + } + break; } } } @@ -795,6 +834,11 @@ 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->HasWidget(WID_RAT_BUILD_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) || + (this->HasWidget(WID_RAT_BUILD_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_EXTENDED_DEPOT))) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + } + this->RaiseButtons(); this->DisableWidget(WID_RAT_REMOVE); this->SetWidgetDirty(WID_RAT_REMOVE); @@ -804,6 +848,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 +860,12 @@ 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->HasWidget(WID_RAT_BUILD_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) && + !(this->HasWidget(WID_RAT_BUILD_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_EXTENDED_DEPOT)) && + RailToolbar_CtrlChanged(this)) return ES_HANDLED; return ES_NOT_HANDLED; } @@ -857,6 +906,27 @@ struct BuildRailToolbarWindow : Window { }, RailToolbarGlobalHotkeys}; }; +/** + * Add the depot icons depending on availability of construction. + * @return Panel with rail depot buttons. + */ +static std::unique_ptr MakeNWidgetRailDepot() +{ + auto hor = std::make_unique(); + + if (HasBit(_settings_game.depot.rail_depot_types, 0)) { + /* Add the widget for building standard rail depot. */ + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_DEPOT, SPR_IMG_DEPOT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT)); + } + + if (HasBit(_settings_game.depot.rail_depot_types, 1)) { + /* Add the widget for building extended rail depot. */ + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_EXTENDED_DEPOT, SPR_IMG_DEPOT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_EXTENDED_TRAIN_DEPOT)); + } + + return hor; +} + static constexpr NWidgetPart _nested_build_rail_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), @@ -879,8 +949,7 @@ static constexpr NWidgetPart _nested_build_rail_widgets[] = { NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), - NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_DEPOT), - SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DEPOT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT_FOR_BUILDING), + NWidgetFunction(MakeNWidgetRailDepot), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_WAYPOINT), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL_TO_WAYPOINT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_STATION), @@ -1699,12 +1768,33 @@ static void ShowSignalBuilder(Window *parent) } struct BuildRailDepotWindow : public PickerWindowBase { - BuildRailDepotWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent) + + BuildRailDepotWindow(WindowDesc &desc, Window *parent, bool extended_depot) : PickerWindowBase(desc, parent) { this->InitNested(TRANSPORT_RAIL); + + /* Fix direction for extended depots. */ + if (extended_depot) { + switch ((BuildRailDepotWidgets)_build_depot_direction) { + case WID_BRAD_DEPOT_NE: + _build_depot_direction++; + break; + case WID_BRAD_DEPOT_NW: + _build_depot_direction--; + break; + default: break; + } + } + 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 +1824,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 +1833,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 */ @@ -1771,9 +1867,30 @@ static WindowDesc _build_depot_desc( _nested_build_depot_widgets ); -static void ShowBuildTrainDepotPicker(Window *parent) +/** Nested widget definition of the build extended rail depot window */ +static const NWidgetPart _nested_build_extended_depot_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_BUILD_DEPOT_TRAIN_ORIENTATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAD_DEPOT_SW), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAD_DEPOT_SE), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _build_extended_depot_desc( + WDP_AUTO, nullptr, 0, 0, + WC_BUILD_DEPOT, WC_BUILD_TOOLBAR, + WDF_CONSTRUCTION, + _nested_build_extended_depot_widgets +); + +static void ShowBuildTrainDepotPicker(Window *parent, bool extended_depot) { - new BuildRailDepotWindow(_build_depot_desc, parent); + new BuildRailDepotWindow(extended_depot ? _build_extended_depot_desc : _build_depot_desc, parent, extended_depot); } class WaypointPickerCallbacks : public PickerCallbacksNewGRFClass { diff --git a/src/rail_map.h b/src/rail_map.h index 5ff2fb9ec3..a715fc67fe 100644 --- a/src/rail_map.h +++ b/src/rail_map.h @@ -23,7 +23,7 @@ enum RailTileType { RAIL_TILE_NORMAL = 0, ///< Normal rail tile without signals RAIL_TILE_SIGNALS = 1, ///< Normal rail tile with signals - RAIL_TILE_DEPOT = 3, ///< Depot (one entrance) + RAIL_TILE_DEPOT = 2, ///< Depot }; /** @@ -107,6 +107,50 @@ debug_inline static bool IsRailDepotTile(Tile t) return IsTileType(t, MP_RAILWAY) && IsRailDepot(t); } +/** + * Is this rail depot tile an extended depot? + * @param t the tile to get the information from + * @pre IsRailDepotTile(t) + * @return true if and only if the tile is an extended rail depot + */ +static inline bool IsExtendedRailDepot(Tile t) +{ + assert(IsRailDepotTile(t)); + return HasBit(t.m5(), 5); +} + +/** + * Is this rail tile a standard rail depot? + * @param t the tile to get the information from + * @pre IsTileType(t, MP_RAILWAY) + * @return true if and only if the tile is a standard rail depot + */ +static inline bool IsStandardRailDepot(Tile t) +{ + assert(IsTileType(t, MP_RAILWAY)); + return IsRailDepot(t) && !IsExtendedRailDepot(t); +} + +/** + * Is this tile a standard rail depot? + * @param t the tile to get the information from + * @return true if and only if the tile is a standard rail depot + */ +static inline bool IsStandardRailDepotTile(TileIndex t) +{ + return IsTileType(t, MP_RAILWAY) && IsStandardRailDepot(t); +} + +/** + * Is this tile rail tile and an extended rail depot? + * @param t the tile to get the information from + * @return true if and only if the tile is an extended rail depot + */ +static inline bool IsExtendedRailDepotTile(TileIndex t) +{ + return IsTileType(t, MP_RAILWAY) && IsRailDepotTile(t) && IsExtendedRailDepot(t); +} + /** * Gets the rail type of the given tile * @param t the tile to get the rail type from 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..77cb9432e7 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -42,6 +42,7 @@ #include "road_cmd.h" #include "landscape_cmd.h" #include "rail_cmd.h" +#include "platform_func.h" #include "table/strings.h" #include "table/roadtypes.h" @@ -313,6 +314,22 @@ CommandCost CheckAllowRemoveRoad(TileIndex tile, RoadBits remove, Owner owner, R return CommandCost(); } +void UpdateRoadDepotDir(TileIndex tile) +{ + assert(IsExtendedRoadDepot(tile)); + RoadBits rb = GetAllRoadBits(tile); + DiagDirection dir = DIAGDIR_NE; + if (rb & ROAD_SE) { + dir = DIAGDIR_SE; + } else if (rb & ROAD_SW) { + dir = DIAGDIR_SW; + } else if (rb & ROAD_NW) { + dir = DIAGDIR_NW; + } else { + assert(rb & ROAD_NE); + } + SetRoadDepotDirection(tile, dir); +} /** * Delete a piece of road. @@ -524,9 +541,33 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec return CommandCost(EXPENSES_CONSTRUCTION, RoadClearCost(existing_rt) * 2); } - default: - case ROAD_TILE_DEPOT: - return CMD_ERROR; + case ROAD_TILE_DEPOT: { + /* Depot must have at least one road bit. */ + RoadBits new_rb = (GetRoadBits(tile, rtt) & ~pieces); + if (new_rb == ROAD_NONE && GetRoadType(tile, OtherRoadTramType(rtt)) == INVALID_ROADTYPE) return CMD_ERROR; + + uint num_removed_bits = CountBits(pieces & GetRoadBits(tile, rtt)); + CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_ROAD] * num_removed_bits); + + if ((flags & DC_EXEC) && num_removed_bits != 0) { + SetRoadBits(tile, new_rb, rtt); + + Company *c = Company::GetIfValid(GetTileOwner(tile)); + c->infrastructure.road[GetRoadType(tile, rtt)] -= num_removed_bits; + DirtyCompanyInfrastructureWindows(c->index); + + if (new_rb == ROAD_NONE) { + SetRoadType(tile, rtt, INVALID_ROADTYPE); + Depot::GetByTile(tile)->AfterAddRemove(TileArea(tile), false); + } + + if (IsExtendedRoadDepot(tile)) UpdateRoadDepotDir(tile); + MarkTileDirtyByTile(tile); + } + return cost; + } + + default: NOT_REACHED(); } } @@ -711,8 +752,34 @@ CommandCost CmdBuildRoad(DoCommandFlag flags, TileIndex tile, RoadBits pieces, R if (HasTileRoadType(tile, rtt)) return_cmd_error(STR_ERROR_ALREADY_BUILT); break; - case ROAD_TILE_DEPOT: - if ((GetAnyRoadBits(tile, rtt) & pieces) == pieces) return_cmd_error(STR_ERROR_ALREADY_BUILT); + case ROAD_TILE_DEPOT: { + Owner owner = GetRoadOwner(tile, rtt); + if (owner != OWNER_NONE) { + CommandCost ret = CheckOwnership(owner, tile); + if (ret.Failed()) return ret; + } + + if (IsExtendedRoadDepot(tile)) { + RoadType tile_rt = GetRoadType(tile, rtt); + if (tile_rt != INVALID_ROADTYPE && rt != tile_rt) return CMD_ERROR; + Axis axis = DiagDirToAxis(GetRoadDepotDirection(tile)); + RoadBits rb = (axis == AXIS_X ? ROAD_X : ROAD_Y) & pieces; + if (rb != pieces) return CMD_ERROR; + existing = GetRoadBits(tile, rtt); + if ((rb & ~existing) == ROAD_NONE) return_cmd_error(STR_ERROR_ALREADY_BUILT); + cost.AddCost(_price[PR_BUILD_DEPOT_ROAD] * CountBits(rb & ~existing)); + break; + } else if (GetRoadBits(tile, OtherRoadTramType(rtt)) == pieces) { + /* Check if we can add a new road/tram type if none present. */ + if (HasTileRoadType(tile, rtt)) { + return_cmd_error(STR_ERROR_ALREADY_BUILT); + } + /* We may add a new road type. */ + cost.AddCost(_price[PR_BUILD_DEPOT_ROAD]); + break; + } + } + goto do_clear; default: NOT_REACHED(); @@ -886,7 +953,18 @@ do_clear:; switch (GetTileType(tile)) { case MP_ROAD: { RoadTileType rttype = GetRoadTileType(tile); - if (existing == ROAD_NONE || rttype == ROAD_TILE_CROSSING) { + if (rttype == ROAD_TILE_DEPOT) { + SetRoadType(tile, rtt, rt); + if (IsExtendedRoadDepot(tile)) { + SetRoadBits(tile, pieces | GetRoadBits(tile, rtt), rtt); + /* Do not add or remove to company infrastructure for depots. Already acounted for. */ + UpdateRoadDepotDir(tile); + } else { + SetRoadBits(tile, GetRoadBits(tile, OtherRoadTramType(rtt)), rtt); + } + Depot::GetByTile(tile)->AfterAddRemove(TileArea(tile), true); + break; + } else if (existing == ROAD_NONE || rttype == ROAD_TILE_CROSSING) { SetRoadType(tile, rtt, rt); SetRoadOwner(tile, rtt, company); if (rtt == RTT_ROAD) SetTownIndex(tile, town_id); @@ -1140,67 +1218,184 @@ std::tuple CmdRemoveLongRoad(DoCommandFlag flags, TileIndex * @param tile tile where to build the depot * @param flags operation to perform * @param rt road type - * @param dir entrance direction + * @param orig_dir entrance direction + * @param adjacent allow adjacent depots + * @param extended build extended depot + * @param half_start build only one trackbit in start tile if building an extended depot + * @param half_end build only one trackbit in end tile if building an extended depot + * @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 orig_dir, bool adjacent, bool extended, bool half_start, bool half_end, DepotID join_to, TileIndex end_tile) { - if (!ValParamRoadType(rt) || !IsValidDiagDirection(dir)) return CMD_ERROR; + if (!ValParamRoadType(rt) || !IsValidDiagDirection(orig_dir)) return CMD_ERROR; + + if (Company::IsValidHumanID(_current_company) && !HasBit(_settings_game.depot.road_depot_types, extended)) { + return_cmd_error(STR_ERROR_DEPOT_TYPE_NOT_AVAILABLE); + } + + TileArea ta(tile, end_tile); + assert(extended || ta.w == 1 || ta.h == 1); + + Axis axis = DiagDirToAxis(orig_dir); + RoadTramType rtt = GetRoadTramType(rt); + uint start_coord = 0; + uint end_coord = 0; + + DiagDirection dir = orig_dir; + if (extended) { + start_coord = axis == AXIS_X ? TileX(tile) : TileY(tile); + end_coord = axis == AXIS_X ? TileX(end_tile) : TileY(end_tile); + + dir = AxisToDiagDir(axis); + + /* Swap direction, also the half-tile drag var (bit 0 and 1) */ + if (start_coord > end_coord || start_coord == end_coord) { + dir = ReverseDiagDir(dir); + half_start = !half_start; + half_end = !half_end; + } + } + + /* 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_overbuilt_depot_tiles = 0; CommandCost cost(EXPENSES_CONSTRUCTION); + int allowed_z = -1; + uint num_new_pieces = 0; + uint invalid_dirs = extended ? 5 << axis : 1 << dir; + for (Tile t : ta) { + RoadBits rb = ROAD_NONE; + 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)) { + auto [tileh, z] = GetTileSlopeZ(t); + int flat_z = z + GetSlopeMaxZ(tileh); + + if (tileh != SLOPE_FLAT) { + if (!_settings_game.construction.build_on_slopes || !CanBuildDepotByTileh(dir, tileh)) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } + + if (extended && IsSteepSlope(tileh)) return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + /* Forbid building if the tile faces a slope in a invalid direction. */ + for (DiagDirection dir = DIAGDIR_BEGIN; dir != DIAGDIR_END; dir++) { + if (HasBit(invalid_dirs, dir) && !CanBuildDepotByTileh(dir, tileh)) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } + } + + cost.AddCost(_price[PR_BUILD_FOUNDATION]); + } + + /* The level of this tile must be equal to allowed_z. */ + if (allowed_z < 0) { + /* First tile. */ + allowed_z = flat_z; + } else if (allowed_z != flat_z) { return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); } - 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; + /* Check whether this is a compatible depot tile. */ + if (IsRoadDepotTile(t) && GetDepotIndex(t) == join_to && rt == GetRoadType(t, rtt)) { + if (extended) { + if (IsExtendedRoadDepotTile(t) && + axis == DiagDirToAxis(GetRoadDepotDirection(t))) { + /* Already exists and has the right axis: Check new roadbits. */ + goto rb_for_extended_depot; + } + } else { + if (!IsExtendedRoadDepotTile(t)) { + if (dir == GetRoadDepotDirection(t)) continue; - if (dir == GetRoadDepotDirection(tile)) return CommandCost(); + /* If another roadtype exists (road/tram), depot cannot be rotated. */ + if (GetRoadTypeRoad(t) != INVALID_ROADTYPE && GetRoadTypeTram(t) != INVALID_ROADTYPE) { + return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED); + } - 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 (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, ROAD_DEPOT_TRACKBIT_FACTOR); + /* Overbuild the depot tile and change its exit direction. */ + num_overbuilt_depot_tiles++; + if (flags & DC_EXEC) { + rb = DiagDirToRoadBits(orig_dir); + SetRoadBits(t, rb, rtt); + SetRoadDepotDirection(t, orig_dir); + MarkTileDirtyByTile(t); + } + continue; + } + } } - MarkTileDirtyByTile(tile); + cost.AddCost(Command::Do(flags, t)); + if (cost.Failed()) return cost; + + /* Check which road bits to build. */ + if (extended) { +rb_for_extended_depot: + uint axis_coord = axis == AXIS_X ? TileX(t) : TileY(t); + /* Road parts only have to be built at the start tile or at the end tile. */ + if (!half_end && axis_coord == end_coord) { + rb = DiagDirToRoadBits(ReverseDiagDir(dir)); + } + if (half_start && axis_coord == start_coord) { + rb = DiagDirToRoadBits(dir); + } + if (rb == ROAD_NONE) { + rb = AxisToRoadBits(axis); + } + assert(rb != ROAD_NONE); + if (IsRoadDepotTile(t)) { + RoadType old_rt = GetRoadType(t, rtt); + if (old_rt != INVALID_ROADTYPE && old_rt != rt) return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED); + RoadBits old_rb = GetAllRoadBits(t); + if ((old_rb & AxisToRoadBits(axis)) != old_rb) return_cmd_error(STR_ERROR_BUILDING_MUST_BE_DEMOLISHED); + old_rb = GetRoadBits(t, rtt); + if ((rb & ~old_rb) == ROAD_NONE) return_cmd_error(STR_ERROR_ALREADY_BUILT); + num_new_pieces += CountBits(rb & ~old_rb); + num_overbuilt_depot_tiles++; + rb |= old_rb; + } else { + num_new_pieces += CountBits(rb); + num_new_depot_tiles++; + } + } else { + rb = DiagDirToRoadBits(orig_dir); + num_new_pieces += 1; + num_new_depot_tiles++; + } + + if (flags & DC_EXEC) { + if (!IsRoadDepotTile(t)) MakeRoadDepot(t, _current_company, depot->index, orig_dir, rt); + if (GetRoadType(t, rtt) == INVALID_ROADTYPE) SetRoadType(t, rtt, rt); + SetRoadBits(t, rb, rtt); + if (extended) { + SB(t.m5(), 5, 1, true); + UpdateRoadDepotDir(t); + } + + MarkTileDirtyByTile(t); + } + } + + if (num_new_depot_tiles + num_overbuilt_depot_tiles == 0) return CommandCost(); + + cost.AddCost(_price[PR_BUILD_DEPOT_ROAD] * (num_new_depot_tiles + num_overbuilt_depot_tiles)); + + if (flags & DC_EXEC) { + UpdateCompanyRoadInfrastructure(rt, _current_company, num_new_pieces); + + depot->AfterAddRemove(ta, true); + if (join_to == NEW_DEPOT) MakeDefaultName(depot); } - cost.AddCost(_price[PR_BUILD_DEPOT_ROAD]); return cost; } @@ -1214,21 +1409,27 @@ static CommandCost RemoveRoadDepot(TileIndex tile, DoCommandFlag flags) CommandCost ret = EnsureNoVehicleOnGround(tile); if (ret.Failed()) return ret; + CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_ROAD]); + RoadType rt = GetRoadTypeRoad(tile); + RoadType tt = GetRoadTypeTram(tile); + if (rt != INVALID_ROADTYPE) cost.AddCost(_price[PR_CLEAR_DEPOT_ROAD]); + if (tt != INVALID_ROADTYPE) cost.AddCost(_price[PR_CLEAR_DEPOT_ROAD]); + 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); - if (rt == INVALID_ROADTYPE) rt = GetRoadTypeTram(tile); - c->infrastructure.road[rt] -= ROAD_DEPOT_TRACKBIT_FACTOR; + /* A road depot has two road types. */ + if (rt != INVALID_ROADTYPE) c->infrastructure.road[rt] -= CountBits(GetRoadBits(tile, RTT_ROAD)); + if (tt != INVALID_ROADTYPE) c->infrastructure.road[tt] -= CountBits(GetRoadBits(tile, RTT_TRAM)); DirtyCompanyInfrastructureWindows(c->index); } - delete Depot::GetByTile(tile); DoClearSquare(tile); + depot->AfterAddRemove(TileArea(tile), false); } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_ROAD]); + return cost; } static CommandCost ClearTile_Road(TileIndex tile, DoCommandFlag flags) @@ -1454,11 +1655,16 @@ void DrawRoadCatenary(const TileInfo *ti) RoadBits tram = ROAD_NONE; if (IsTileType(ti->tile, MP_ROAD)) { - if (IsNormalRoad(ti->tile)) { - road = GetRoadBits(ti->tile, RTT_ROAD); - tram = GetRoadBits(ti->tile, RTT_TRAM); - } else if (IsLevelCrossing(ti->tile)) { - tram = road = (GetCrossingRailAxis(ti->tile) == AXIS_Y ? ROAD_X : ROAD_Y); + switch (GetRoadTileType(ti->tile)) { + case ROAD_TILE_NORMAL: + case ROAD_TILE_DEPOT: + road = GetRoadBits(ti->tile, RTT_ROAD); + tram = GetRoadBits(ti->tile, RTT_TRAM); + break; + case ROAD_TILE_CROSSING: + tram = road = (GetCrossingRailAxis(ti->tile) == AXIS_Y ? ROAD_X : ROAD_Y); + break; + default: NOT_REACHED(); } } else if (IsTileType(ti->tile, MP_STATION)) { if (IsAnyRoadStop(ti->tile)) { @@ -1821,17 +2027,30 @@ static void DrawTile_Road(TileInfo *ti) case ROAD_TILE_DEPOT: { if (ti->tileh != SLOPE_FLAT) DrawFoundation(ti, FOUNDATION_LEVELED); - PaletteID palette = COMPANY_SPRITE_COLOUR(GetTileOwner(ti->tile)); - RoadType road_rt = GetRoadTypeRoad(ti->tile); RoadType tram_rt = GetRoadTypeTram(ti->tile); - const RoadTypeInfo *rti = GetRoadTypeInfo(road_rt == INVALID_ROADTYPE ? tram_rt : road_rt); + assert(road_rt != INVALID_ROADTYPE || tram_rt != INVALID_ROADTYPE); + const RoadTypeInfo *road_rti = road_rt != INVALID_ROADTYPE ? GetRoadTypeInfo(road_rt) : nullptr; + const RoadTypeInfo *tram_rti = tram_rt != INVALID_ROADTYPE ? GetRoadTypeInfo(tram_rt) : nullptr; + const RoadTypeInfo *main_rti = tram_rti != nullptr ? tram_rti : road_rti; - int relocation = GetCustomRoadSprite(rti, ti->tile, ROTSG_DEPOT); + DiagDirection dir = GetRoadDepotDirection(ti->tile); + uint road_offset = GetRoadSpriteOffset(SLOPE_FLAT, GetRoadBits(ti->tile, RTT_ROAD)); + uint tram_offset = GetRoadSpriteOffset(SLOPE_FLAT, GetRoadBits(ti->tile, RTT_TRAM)); + + + PaletteID pal = PAL_NONE; + const DrawTileSprites *dts = &_road_depot[dir]; + SpriteID image = SPR_ROAD_Y + (road_rti == nullptr ? tram_offset : road_offset) - 19; + DrawGroundSprite(image, pal); + + DrawRoadOverlays(ti, pal, road_rti, tram_rti, road_offset, tram_offset); + + int relocation = GetCustomRoadSprite(main_rti, ti->tile, ROTSG_DEPOT); bool default_gfx = relocation == 0; if (default_gfx) { - if (HasBit(rti->flags, ROTF_CATENARY)) { - if (_loaded_newgrf_features.tram == TRAMWAY_REPLACE_DEPOT_WITH_TRACK && road_rt == INVALID_ROADTYPE && !rti->UsesOverlay()) { + if (HasBit(main_rti->flags, ROTF_CATENARY)) { + if (_loaded_newgrf_features.tram == TRAMWAY_REPLACE_DEPOT_WITH_TRACK && road_rt == INVALID_ROADTYPE && !main_rti->UsesOverlay()) { /* Sprites with track only work for default tram */ relocation = SPR_TRAMWAY_DEPOT_WITH_TRACK - SPR_ROAD_DEPOT; default_gfx = false; @@ -1844,21 +2063,11 @@ static void DrawTile_Road(TileInfo *ti) relocation -= SPR_ROAD_DEPOT; } - DiagDirection dir = GetRoadDepotDirection(ti->tile); - const DrawTileSprites *dts = &_road_depot[dir]; - DrawGroundSprite(dts->ground.sprite, PAL_NONE); + /* Draw road, tram catenary */ + DrawRoadCatenary(ti); - if (default_gfx) { - uint offset = GetRoadSpriteOffset(SLOPE_FLAT, DiagDirToRoadBits(dir)); - if (rti->UsesOverlay()) { - SpriteID ground = GetCustomRoadSprite(rti, ti->tile, ROTSG_OVERLAY); - if (ground != 0) DrawGroundSprite(ground + offset, PAL_NONE); - } else if (road_rt == INVALID_ROADTYPE) { - DrawGroundSprite(SPR_TRAMWAY_OVERLAY + offset, PAL_NONE); - } - } + DrawRailTileSeq(ti, dts, TO_BUILDINGS, relocation, 0, COMPANY_SPRITE_COLOUR(GetTileOwner(ti->tile))); - DrawRailTileSeq(ti, dts, TO_BUILDINGS, relocation, 0, palette); break; } } @@ -1876,7 +2085,9 @@ void DrawRoadDepotSprite(int x, int y, DiagDirection dir, RoadType rt) { PaletteID palette = COMPANY_SPRITE_COLOUR(_local_company); + RoadTramType rtt = GetRoadTramType(rt); const RoadTypeInfo *rti = GetRoadTypeInfo(rt); + uint road_offset = GetRoadSpriteOffset(SLOPE_FLAT, DiagDirToRoadBits(dir)); int relocation = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_DEPOT); bool default_gfx = relocation == 0; if (default_gfx) { @@ -1897,6 +2108,18 @@ void DrawRoadDepotSprite(int x, int y, DiagDirection dir, RoadType rt) const DrawTileSprites *dts = &_road_depot[dir]; DrawSprite(dts->ground.sprite, PAL_NONE, x, y); + if (rti->UsesOverlay()) { + SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_GROUND); + DrawSprite(ground + road_offset, PAL_NONE, x, y); + ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_OVERLAY); + if (ground != 0) DrawSprite(ground + road_offset, PAL_NONE, x, y); + } else if (rtt == RTT_TRAM) { + DrawSprite(SPR_TRAMWAY_TRAM + road_offset, PAL_NONE, x, y); + DrawSprite(SPR_TRAMWAY_OVERLAY + road_offset, PAL_NONE, x, y); + } else { + DrawSprite(SPR_ROAD_Y + road_offset - 19, PAL_NONE, x, y); + } + if (default_gfx) { uint offset = GetRoadSpriteOffset(SLOPE_FLAT, DiagDirToRoadBits(dir)); if (rti->UsesOverlay()) { @@ -2081,7 +2304,7 @@ static bool ClickTile_Road(TileIndex tile) { if (!IsRoadDepot(tile)) return false; - ShowDepotWindow(tile, VEH_ROAD); + ShowDepotWindow(GetDepotIndex(tile)); return true; } @@ -2153,11 +2376,11 @@ static TrackStatus GetTileTrackStatus_Road(TileIndex tile, TransportType mode, u default: case ROAD_TILE_DEPOT: { - DiagDirection dir = GetRoadDepotDirection(tile); + Axis axis = DiagDirToAxis(GetRoadDepotDirection(tile)); - if (side != INVALID_DIAGDIR && side != dir) break; + if (side != INVALID_DIAGDIR && axis != DiagDirToAxis(side)) break; - trackdirbits = TrackBitsToTrackdirBits(DiagDirToDiagTrackBits(dir)); + trackdirbits = TrackBitsToTrackdirBits(AxisToTrackBits(axis)); break; } } @@ -2214,7 +2437,7 @@ static void GetTileDesc_Road(TileIndex tile, TileDesc *td) } case ROAD_TILE_DEPOT: - td->str = STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT; + td->str = IsExtendedDepot(tile) ? STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT_EXTENDED : STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT; td->build_date = Depot::GetByTile(tile)->build_date; break; @@ -2254,29 +2477,53 @@ static const uint8_t _roadveh_enter_depot_dir[4] = { TRACKDIR_X_SW, TRACKDIR_Y_NW, TRACKDIR_X_NE, TRACKDIR_Y_SE }; -static VehicleEnterTileStatus VehicleEnter_Road(Vehicle *v, TileIndex tile, int, int) +static VehicleEnterTileStatus VehicleEnter_Road(Vehicle *v, TileIndex tile, int x, int y) { - switch (GetRoadTileType(tile)) { - case ROAD_TILE_DEPOT: { - if (v->type != VEH_ROAD) break; + if (GetRoadTileType(tile) != ROAD_TILE_DEPOT || v->type != VEH_ROAD) return VETSB_CONTINUE; - RoadVehicle *rv = RoadVehicle::From(v); - if (rv->frame == RVC_DEPOT_STOP_FRAME && - _roadveh_enter_depot_dir[GetRoadDepotDirection(tile)] == rv->state) { - rv->state = RVSB_IN_DEPOT; - rv->vehstatus |= VS_HIDDEN; - rv->direction = ReverseDir(rv->direction); - if (rv->Next() == nullptr) VehicleEnterDepot(rv->First()); - rv->tile = tile; - - InvalidateWindowData(WC_VEHICLE_DEPOT, rv->tile); - return VETSB_ENTERED_WORMHOLE; + if (IsExtendedRoadDepot(tile)) { + v = v->First(); + if (!IsExtendedRoadDepotTile(v->tile)) return VETSB_CONTINUE; + DepotID depot_id = GetDepotIndex(v->tile); + if (!v->current_order.IsType(OT_GOTO_DEPOT) || + v->current_order.GetDestination() != depot_id) { + return VETSB_CONTINUE; + } + for (Vehicle *u = v; u != nullptr; u = u->Next()) { + if (!IsExtendedRoadDepotTile(u->tile) || GetDepotIndex(u->tile) != depot_id) return VETSB_CONTINUE; + if (!IsDiagonalDirection(u->direction)) return VETSB_CONTINUE; + if (DiagDirToAxis(DirToDiagDir(u->direction)) != + DiagDirToAxis(GetRoadDepotDirection(v->tile))) { + return VETSB_CONTINUE; } - break; } - default: break; + /* Stop position on platform is half the front vehicle length of the road vehicle. */ + int stop_pos = RoadVehicle::From(v)->gcache.cached_veh_length / 2; + DiagDirection dir = DirToDiagDir(v->direction); + int depot_ahead = (GetPlatformLength(tile, dir, GetRoadTramType(RoadVehicle::From(v)->roadtype)) - 1) * TILE_SIZE; + if (depot_ahead > stop_pos) return VETSB_CONTINUE; + + x = v->x_pos & 0xF; + y = v->y_pos & 0xF; + + if (DiagDirToAxis(dir) != AXIS_X) Swap(x, y); + if (dir == DIAGDIR_SE || dir == DIAGDIR_SW) x = TILE_SIZE - x; + if (abs(stop_pos - x) <= 1) return VETSB_ENTERED_DEPOT_PLATFORM; + } else { + RoadVehicle *rv = RoadVehicle::From(v); + if (rv->frame == RVC_DEPOT_STOP_FRAME && + _roadveh_enter_depot_dir[GetRoadDepotDirection(tile)] == rv->state) { + rv->state = RVSB_IN_DEPOT; + rv->vehstatus |= VS_HIDDEN; + rv->direction = ReverseDir(rv->direction); + if (rv->Next() == nullptr) VehicleEnterDepot(rv->First()); + rv->tile = tile; + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(rv->tile)); + return VETSB_ENTERED_WORMHOLE; + } } + return VETSB_CONTINUE; } @@ -2288,14 +2535,14 @@ static void ChangeTileOwner_Road(TileIndex tile, Owner old_owner, Owner new_owne if (new_owner == INVALID_OWNER) { Command::Do(DC_EXEC | DC_BANKRUPT, tile); } else { - /* A road depot has two road bits. No need to dirty windows here, we'll redraw the whole screen anyway. */ - RoadType rt = GetRoadTypeRoad(tile); - if (rt == INVALID_ROADTYPE) rt = GetRoadTypeTram(tile); - Company::Get(old_owner)->infrastructure.road[rt] -= 2; - Company::Get(new_owner)->infrastructure.road[rt] += 2; - SetTileOwner(tile, new_owner); for (RoadTramType rtt : _roadtramtypes) { + RoadType rt = GetRoadTypeRoad(tile); + if (rt != INVALID_ROADTYPE) { + uint pieces = CountBits(GetRoadBits(tile, rtt)); + Company::Get(old_owner)->infrastructure.road[rt] -= pieces; + Company::Get(new_owner)->infrastructure.road[rt] += pieces; + } if (GetRoadOwner(tile, rtt) == old_owner) { SetRoadOwner(tile, rtt, new_owner); } @@ -2344,7 +2591,10 @@ static CommandCost TerraformTile_Road(TileIndex tile, DoCommandFlag flags, int z break; case ROAD_TILE_DEPOT: - if (AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, GetRoadDepotDirection(tile))) return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + if (AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, GetRoadDepotDirection(tile)) && + (!IsExtendedRoadDepot(tile) || AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, ReverseDiagDir(GetRoadDepotDirection(tile))))) { + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + } break; case ROAD_TILE_NORMAL: { @@ -2521,8 +2771,6 @@ CommandCost CmdConvertRoad(DoCommandFlag flags, TileIndex tile, TileIndex area_s uint num_pieces = CountBits(GetAnyRoadBits(tile, rtt)); if (tt == MP_STATION && IsBayRoadStopTile(tile)) { num_pieces *= ROAD_STOP_TRACKBIT_FACTOR; - } else if (tt == MP_ROAD && IsRoadDepot(tile)) { - num_pieces *= ROAD_DEPOT_TRACKBIT_FACTOR; } found_convertible_road = true; @@ -2543,8 +2791,8 @@ 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); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(tile)); + InvalidateWindowData(WC_BUILD_VEHICLE, GetDepotIndex(tile)); } } } else { diff --git a/src/road_cmd.h b/src/road_cmd.h index 71883ddada..b7a54c5c4a 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, bool extended, bool half_start, bool half_end, 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, bool extended, bool half_start, bool half_end, 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..4b99c39f84 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" @@ -51,7 +52,7 @@ #include "safeguards.h" static void ShowRVStationPicker(Window *parent, RoadStopType rs); -static void ShowRoadDepotPicker(Window *parent); +static void ShowRoadDepotPicker(Window *parent, bool extended_depot); static void ShowBuildRoadWaypointPicker(Window *parent); static bool _remove_button_clicked; @@ -170,13 +171,46 @@ 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 orig_dir, bool, bool extended, bool half_start, bool half_end, 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); + + Axis axis = DiagDirToAxis(orig_dir); + uint start_coord; + uint end_coord; + bool build_start = true; + bool build_end = false; + + DiagDirection dir = orig_dir; + if (extended) { + build_start = half_end; + build_end = !half_start; + start_coord = axis == AXIS_X ? TileX(start_tile) : TileY(start_tile); + end_coord = axis == AXIS_X ? TileX(end_tile) : TileY(end_tile); + + dir = AxisToDiagDir(axis); + + /* Swap direction, also the half-tile drag var (bit 0 and 1) */ + if (start_coord > end_coord || start_coord == end_coord) { + dir = ReverseDiagDir(dir); + build_start = !build_start; + build_end = !build_end; + } + } + + + TileArea ta(start_tile, end_tile); + for (TileIndex tile : ta) { + if (build_start && !ta.Contains(tile + TileOffsByDiagDir(dir))) { + ConnectRoadToStructure(tile, dir); + } + if (build_end && !ta.Contains(tile + TileOffsByDiagDir(ReverseDiagDir(dir)))) { + ConnectRoadToStructure(tile, ReverseDiagDir(dir)); + } + } } /** @@ -368,6 +402,13 @@ 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->HasWidget(WID_ROT_DEPOT) && this->IsWidgetLowered(WID_ROT_DEPOT)) || + (this->HasWidget(WID_ROT_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_ROT_EXTENDED_DEPOT)))) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + } + this->Window::Close(); } @@ -384,6 +425,7 @@ struct BuildRoadToolbarWindow : Window { bool can_build = CanBuildVehicleInfrastructure(VEH_ROAD, rtt); this->SetWidgetsDisabledState(!can_build, WID_ROT_DEPOT, + WID_ROT_EXTENDED_DEPOT, WID_ROT_BUILD_WAYPOINT, WID_ROT_BUS_STATION, WID_ROT_TRUCK_STATION); @@ -397,12 +439,14 @@ struct BuildRoadToolbarWindow : Window { if (_game_mode != GM_EDITOR) { if (!can_build) { /* Show in the tooltip why this button is disabled. */ - this->GetWidget(WID_ROT_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); + if (this->HasWidget(WID_ROT_DEPOT)) this->GetWidget(WID_ROT_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); + if (this->HasWidget(WID_ROT_EXTENDED_DEPOT)) this->GetWidget(WID_ROT_EXTENDED_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_ROT_BUILD_WAYPOINT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_ROT_BUS_STATION)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); this->GetWidget(WID_ROT_TRUCK_STATION)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE); } else { - this->GetWidget(WID_ROT_DEPOT)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT); + if (this->HasWidget(WID_ROT_DEPOT)) this->GetWidget(WID_ROT_DEPOT)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT); + if (this->HasWidget(WID_ROT_EXTENDED_DEPOT)) this->GetWidget(WID_ROT_EXTENDED_DEPOT)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_EXTENDED_ROAD_VEHICLE_DEPOT : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_EXTENDED_TRAM_VEHICLE_DEPOT); this->GetWidget(WID_ROT_BUILD_WAYPOINT)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_ROAD_TO_WAYPOINT : STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_TRAM_TO_WAYPOINT); this->GetWidget(WID_ROT_BUS_STATION)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_BUS_STATION : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_PASSENGER_TRAM_STATION); this->GetWidget(WID_ROT_TRUCK_STATION)->SetToolTip(rtt == RTT_ROAD ? STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRUCK_LOADING_BAY : STR_ROAD_TOOLBAR_TOOLTIP_BUILD_CARGO_TRAM_STATION); @@ -427,7 +471,8 @@ struct BuildRoadToolbarWindow : Window { this->GetWidget(WID_ROT_ROAD_Y)->widget_data = rti->gui_sprites.build_y_road; this->GetWidget(WID_ROT_AUTOROAD)->widget_data = rti->gui_sprites.auto_road; if (_game_mode != GM_EDITOR) { - this->GetWidget(WID_ROT_DEPOT)->widget_data = rti->gui_sprites.build_depot; + if (this->HasWidget(WID_ROT_DEPOT)) this->GetWidget(WID_ROT_DEPOT)->widget_data = rti->gui_sprites.build_depot; + if (this->HasWidget(WID_ROT_EXTENDED_DEPOT)) this->GetWidget(WID_ROT_EXTENDED_DEPOT)->widget_data = rti->gui_sprites.build_depot; } this->GetWidget(WID_ROT_CONVERT_ROAD)->widget_data = rti->gui_sprites.convert_road; this->GetWidget(WID_ROT_BUILD_TUNNEL)->widget_data = rti->gui_sprites.build_tunnel; @@ -538,8 +583,9 @@ struct BuildRoadToolbarWindow : Window { break; case WID_ROT_DEPOT: - if (HandlePlacePushButton(this, WID_ROT_DEPOT, this->rti->cursor.depot, HT_RECT)) { - ShowRoadDepotPicker(this); + case WID_ROT_EXTENDED_DEPOT: + if (HandlePlacePushButton(this, widget, this->rti->cursor.depot, HT_RECT)) { + ShowRoadDepotPicker(this, widget == WID_ROT_EXTENDED_DEPOT); this->last_started_action = widget; } break; @@ -636,9 +682,19 @@ struct BuildRoadToolbarWindow : Window { break; case WID_ROT_DEPOT: - Command::Post(this->rti->strings.err_depot, CcRoadDepot, - tile, _cur_roadtype, _road_depot_orientation); + case WID_ROT_EXTENDED_DEPOT: { + CloseWindowById(WC_SELECT_DEPOT, VEH_ROAD); + + _place_road_dir = DiagDirToAxis(_road_depot_orientation); + _place_road_start_half_x = (_place_road_dir == AXIS_X) && (_tile_fract_coords.x >= 8); + _place_road_start_half_y = (_place_road_dir == AXIS_Y) && (_tile_fract_coords.y >= 8); + + VpSetPlaceSizingLimit(_settings_game.depot.depot_spread); + ViewportPlaceMethod vpm = VPM_X_AND_Y_LIMITED; + if (this->last_started_action == WID_ROT_DEPOT) vpm = (DiagDirToAxis(_road_depot_orientation) == AXIS_X) ? VPM_X_LIMITED : VPM_Y_LIMITED; + VpStartPlaceSizing(tile, vpm, DDSP_BUILD_DEPOT); break; + } case WID_ROT_BUILD_WAYPOINT: PlaceRoad_Waypoint(tile); @@ -673,6 +729,12 @@ 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->HasWidget(WID_ROT_DEPOT) && this->IsWidgetLowered(WID_ROT_DEPOT)) || + (this->HasWidget(WID_ROT_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_ROT_EXTENDED_DEPOT)))) { + SetViewportHighlightDepot(INVALID_DEPOT, true); + } + this->RaiseButtons(); this->SetWidgetDisabledState(WID_ROT_REMOVE, true); this->SetWidgetDirty(WID_ROT_REMOVE); @@ -687,6 +749,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); } @@ -719,7 +782,14 @@ struct BuildRoadToolbarWindow : Window { _place_road_dir = AXIS_Y; _place_road_end_half = pt.y & 8; } + break; + case DDSP_BUILD_DEPOT: + if (_place_road_dir == AXIS_X) { + _place_road_end_half = pt.x & 8; + } else { + _place_road_end_half = pt.y & 8; + } break; default: @@ -805,6 +875,21 @@ struct BuildRoadToolbarWindow : Window { } break; + case DDSP_BUILD_DEPOT: { + StringID error_string = this->rti->strings.err_depot; + bool adjacent = _ctrl_pressed; + bool extended = last_started_action == WID_ROT_EXTENDED_DEPOT; + bool half_start = _place_road_start_half_x || _place_road_start_half_y; + bool half_end = _place_road_end_half; + + auto proc = [=](DepotID join_to) -> bool { + return Command::Post(error_string, CcRoadDepot, start_tile, _cur_roadtype, _road_depot_orientation, adjacent, extended, half_start, half_end, 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; @@ -900,6 +985,27 @@ struct BuildRoadToolbarWindow : Window { }, TramToolbarGlobalHotkeys}; }; +/** + * Add the depot icons depending on availability of construction. + * @return Panel with road depot buttons. + */ +static std::unique_ptr MakeNWidgetRoadDepot() +{ + auto hor = std::make_unique(); + + if (HasBit(_settings_game.depot.road_depot_types, 0)) { + /* Add the widget for building standard road depots. */ + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_DEPOT, SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT)); + } + + if (HasBit(_settings_game.depot.road_depot_types, 1)) { + /* Add the widget for building extended road depots. */ + hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_EXTENDED_DEPOT, SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_EXTENDED_ROAD_VEHICLE_DEPOT)); + } + + return hor; +} + static constexpr NWidgetPart _nested_build_road_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), @@ -915,8 +1021,7 @@ static constexpr NWidgetPart _nested_build_road_widgets[] = { SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTOROAD, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOROAD), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), - NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_DEPOT), - SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT), + NWidgetFunction(MakeNWidgetRoadDepot), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_WAYPOINT), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_ROAD_TO_WAYPOINT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUS_STATION), @@ -960,8 +1065,7 @@ static constexpr NWidgetPart _nested_build_tramway_widgets[] = { SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTOTRAM, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOTRAM), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), - NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_DEPOT), - SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_ROAD_DEPOT, STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT), + NWidgetFunction(MakeNWidgetRoadDepot), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUILD_WAYPOINT), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_TRAM_TO_WAYPOINT), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_ROT_BUS_STATION), @@ -1091,14 +1195,28 @@ Window *ShowBuildRoadScenToolbar(RoadType roadtype) } struct BuildRoadDepotWindow : public PickerWindowBase { - BuildRoadDepotWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent) + BuildRoadDepotWindow(WindowDesc &desc, Window *parent, bool extended_depot) : PickerWindowBase(desc, parent) { this->CreateNestedTree(); + /* Fix direction for extended depots. */ + if (extended_depot) { + switch (_road_depot_orientation) { + case DIAGDIR_NE: + _road_depot_orientation++; + break; + case DIAGDIR_NW: + _road_depot_orientation--; + break; + default: break; + } + } + this->LowerWidget(WID_BROD_DEPOT_NE + _road_depot_orientation); if (RoadTypeIsTram(_cur_roadtype)) { this->GetWidget(WID_BROD_CAPTION)->widget_data = STR_BUILD_DEPOT_TRAM_ORIENTATION_CAPTION; for (WidgetID i = WID_BROD_DEPOT_NE; i <= WID_BROD_DEPOT_NW; i++) { + if (!this->HasWidget(i)) continue; this->GetWidget(i)->tool_tip = STR_BUILD_DEPOT_TRAM_ORIENTATION_SELECT_TOOLTIP; } } @@ -1106,6 +1224,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 +1259,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 +1271,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[] = { @@ -1174,9 +1304,29 @@ static WindowDesc _build_road_depot_desc( _nested_build_road_depot_widgets ); -static void ShowRoadDepotPicker(Window *parent) +static const NWidgetPart _nested_build_extended_road_depot_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BROD_CAPTION), SetDataTip(STR_BUILD_DEPOT_ROAD_ORIENTATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), + NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROD_DEPOT_SW), SetMinimalSize(66, 50), SetFill(0, 0), SetDataTip(0x0, STR_BUILD_DEPOT_ROAD_ORIENTATION_SELECT_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROD_DEPOT_SE), SetMinimalSize(66, 50), SetFill(0, 0), SetDataTip(0x0, STR_BUILD_DEPOT_ROAD_ORIENTATION_SELECT_TOOLTIP), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _build_extended_road_depot_desc( + WDP_AUTO, nullptr, 0, 0, + WC_BUILD_DEPOT, WC_BUILD_TOOLBAR, + WDF_CONSTRUCTION, + _nested_build_extended_road_depot_widgets +); + +static void ShowRoadDepotPicker(Window *parent, bool extended_depot) { - new BuildRoadDepotWindow(_build_road_depot_desc, parent); + new BuildRoadDepotWindow(extended_depot ? _build_extended_road_depot_desc : _build_road_depot_desc, parent, extended_depot); } template diff --git a/src/road_map.cpp b/src/road_map.cpp index 66fe49010f..9fb6b03dac 100644 --- a/src/road_map.cpp +++ b/src/road_map.cpp @@ -38,9 +38,11 @@ RoadBits GetAnyRoadBits(Tile tile, RoadTramType rtt, bool straight_tunnel_bridge case MP_ROAD: switch (GetRoadTileType(tile)) { default: - case ROAD_TILE_NORMAL: return GetRoadBits(tile, rtt); - case ROAD_TILE_CROSSING: return GetCrossingRoadBits(tile); - case ROAD_TILE_DEPOT: return DiagDirToRoadBits(GetRoadDepotDirection(tile)); + case ROAD_TILE_NORMAL: + case ROAD_TILE_DEPOT: + return GetRoadBits(tile, rtt); + case ROAD_TILE_CROSSING: + return GetCrossingRoadBits(tile); } case MP_STATION: diff --git a/src/road_map.h b/src/road_map.h index 06c000384c..ac184a25f2 100644 --- a/src/road_map.h +++ b/src/road_map.h @@ -118,16 +118,38 @@ debug_inline static bool IsRoadDepotTile(Tile t) return IsTileType(t, MP_ROAD) && IsRoadDepot(t); } +/** + * Return whether a road depot tile is an extended one. + * @param t Tile to query. + * @return True if extended road depot tile. + */ +static inline bool IsExtendedRoadDepot(Tile t) +{ + assert(IsTileType(t, MP_ROAD)); + assert(IsRoadDepot(t)); + return HasBit(t.m5(), 5); +} + +/** + * Return whether a tile is an extended road depot tile. + * @param t Tile to query. + * @return True if extended road depot tile. + */ +static inline bool IsExtendedRoadDepotTile(Tile t) +{ + return IsTileType(t, MP_ROAD) && IsRoadDepot(t) && IsExtendedRoadDepot(t); +} + /** * Get the present road bits for a specific road type. * @param t The tile to query. - * @param rt Road type. - * @pre IsNormalRoad(t) + * @param rtt Road tram type. + * @pre IsNormalRoad(t) || IsRoadDepotTile(t) * @return The present road bits for the road type. */ inline RoadBits GetRoadBits(Tile t, RoadTramType rtt) { - assert(IsNormalRoad(t)); + assert(IsNormalRoad(t) || IsRoadDepotTile(t)); if (rtt == RTT_TRAM) return (RoadBits)GB(t.m3(), 0, 4); return (RoadBits)GB(t.m5(), 0, 4); } @@ -152,7 +174,7 @@ inline RoadBits GetAllRoadBits(Tile tile) */ inline void SetRoadBits(Tile t, RoadBits r, RoadTramType rtt) { - assert(IsNormalRoad(t)); // XXX incomplete + assert(IsNormalRoad(t) || IsRoadDepotTile(t)); if (rtt == RTT_TRAM) { SB(t.m3(), 0, 4, r); } else { @@ -556,19 +578,28 @@ inline void TerminateRoadWorks(Tile t) SB(t.m7(), 0, 4, 0); } +/** + * Set the direction of the exit of a road depot. + * @param t The tile to query. + * @return Diagonal direction of the depot exit. + */ +static inline void SetRoadDepotDirection(Tile t, DiagDirection dir) +{ + assert(IsRoadDepot(t)); + SB(t.m6(), 6, 2, dir); +} /** - * Get the direction of the exit of a road depot. + * Get the direction of the exit of a road depot (or the image of the depot for extended road depots). * @param t The tile to query. * @return Diagonal direction of the depot exit. */ inline DiagDirection GetRoadDepotDirection(Tile t) { assert(IsRoadDepot(t)); - return (DiagDirection)GB(t.m5(), 0, 2); + return (DiagDirection)GB(t.m6(), 6, 2); } - RoadBits GetAnyRoadBits(Tile tile, RoadTramType rtt, bool straight_tunnel_bridge_entrance = false); /** @@ -680,7 +711,7 @@ inline void MakeRoadCrossing(Tile t, Owner road, Owner tram, Owner rail, Axis ro inline void SetRoadDepotExitDirection(Tile tile, DiagDirection dir) { assert(IsRoadDepotTile(tile)); - SB(tile.m5(), 0, 2, dir); + SB(tile.m6(), 6, 2, dir); } /** @@ -698,8 +729,9 @@ inline void MakeRoadDepot(Tile tile, Owner owner, DepotID depot_id, DiagDirectio tile.m2() = depot_id; tile.m3() = 0; tile.m4() = INVALID_ROADTYPE; - tile.m5() = ROAD_TILE_DEPOT << 6 | dir; - SB(tile.m6(), 2, 4, 0); + tile.m5() = ROAD_TILE_DEPOT << 6; + SB(tile.m6(), 0, 6, 0); + SB(tile.m6(), 6, 2, dir); tile.m7() = owner; tile.m8() = INVALID_ROADTYPE << 6; SetRoadType(tile, GetRoadTramType(rt), rt); diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 0cc513bb05..471485fedd 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,62 @@ 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[in,out] is_exit_facing_south Whether the depot tile is facing south. + * @param e Engine to be built. + * @param already_built Whether the vehicle already exists (for vehicle replacement). + * @return CommandCost() or an error message if the depot has no appropriate tiles. + */ +CommandCost FindDepotTileForPlacingEngine(TileIndex &tile, bool &is_exit_facing_south, const Engine *e, bool already_built) +{ + 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 or trying to leave the depot. */ + if (HasTileAnyRoadType(tile, rti->powered_roadtypes) && already_built) return CommandCost(); + + for (auto t : dep->depot_tiles) { + if (!HasTileAnyRoadType(t, rti->powered_roadtypes)) continue; + if (!IsExtendedDepot(t)) return CommandCost(); + if (GetDepotReservation(t, is_exit_facing_south) == DEPOT_RESERVATION_EMPTY) { + tile = t; + return CommandCost(); + } else if (GetDepotReservation(t, !is_exit_facing_south) == DEPOT_RESERVATION_EMPTY) { + is_exit_facing_south = !is_exit_facing_south; + tile = t; + return CommandCost(); + } + } + + return_cmd_error(STR_ERROR_DEPOT_FULL_DEPOT); +} + +DiagDirection GetRoadDepotExit(TileIndex tile, RoadTramType rtt, DiagDirection dir) +{ + assert(IsRoadDepot(tile)); + RoadBits rb = GetRoadBits(tile, rtt); + if ((rb & DiagDirToRoadBits(dir)) != ROAD_NONE) return dir; + if (rb & ROAD_SE) return DIAGDIR_SE; + if (rb & ROAD_SW) return DIAGDIR_SW; + if (rb & ROAD_NE) return DIAGDIR_NE; + if (rb & ROAD_NW) return DIAGDIR_NW; + return INVALID_DIAGDIR; +} + +struct RoadDriveEntry { + uint8_t x, y; +}; + +#include "table/roadveh_movement.h" + /** * Build a road vehicle. * @param flags type of operation. @@ -260,25 +317,39 @@ 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); + DiagDirection dir = GetRoadDepotExit(tile, RoadTramType(rt), GetRoadDepotDirection(tile)); + bool facing_south = IsValidDiagDirection(dir) ? IsDiagDirFacingSouth(dir) : false; + + if ((flags & DC_AUTOREPLACE) == 0) { + CommandCost check = FindDepotTileForPlacingEngine(tile, facing_south, e, false); + if (check.Failed()) return check; + dir = GetRoadDepotExit(tile, RoadTramType(rt), GetRoadDepotDirection(tile)); + } + + if (IsExtendedRoadDepotTile(tile) && facing_south != IsDiagDirFacingSouth(dir)) dir = ReverseDiagDir(dir); if (flags & DC_EXEC) { const RoadVehicleInfo *rvi = &e->u.road; RoadVehicle *v = new RoadVehicle(); *ret = v; - v->direction = DiagDirToDir(GetRoadDepotDirection(tile)); + v->direction = DiagDirToDir(dir); v->owner = _current_company; v->tile = tile; - int x = TileX(tile) * TILE_SIZE + TILE_SIZE / 2; - int y = TileY(tile) * TILE_SIZE + TILE_SIZE / 2; - v->x_pos = x; - v->y_pos = y; - v->z_pos = GetSlopePixelZ(x, y, true); + if (IsExtendedRoadDepotTile(tile)) { + const RoadDriveEntry *rdp = _road_drive_data[GetRoadTramType(rt)][(_settings_game.vehicle.road_side << RVS_DRIVE_SIDE) + DiagDirToDiagTrackdir(dir)]; + v->frame = RVC_DEPOT_START_FRAME; + v->x_pos = TileX(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].x & 0xF); + v->y_pos = TileY(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].y & 0xF); + } else { + v->x_pos = TileX(tile) * TILE_SIZE + TILE_SIZE / 2; + v->y_pos = TileY(tile) * TILE_SIZE + TILE_SIZE / 2; + } + v->z_pos = GetSlopePixelZ(v->x_pos, v->y_pos, true); v->state = RVSB_IN_DEPOT; v->vehstatus = VS_HIDDEN | VS_STOPPED | VS_DEFPAL; @@ -322,6 +393,7 @@ CommandCost CmdBuildRoadVehicle(DoCommandFlag flags, TileIndex tile, const Engin for (RoadVehicle *u = v; u != nullptr; u = u->Next()) { u->cargo_cap = u->GetEngine()->DetermineCapacity(u); u->refit_cap = 0; + u->state = RVSB_IN_DEPOT; v->InvalidateNewGRFCache(); u->InvalidateNewGRFCache(); } @@ -331,6 +403,12 @@ CommandCost CmdBuildRoadVehicle(DoCommandFlag flags, TileIndex tile, const Engin v->UpdatePosition(); + if (IsExtendedDepot(v->tile) && (flags & DC_AUTOREPLACE) == 0) { + v->vehstatus &= ~VS_HIDDEN; + UpdateExtendedDepotReservation(v, true); + v->UpdateViewport(true, true); + } + CheckConsistencyOfArticulatedVehicle(v); } @@ -616,34 +694,56 @@ struct RoadVehFindData { static Vehicle *EnumCheckRoadVehClose(Vehicle *v, void *data) { - static const int8_t dist_x[] = { -4, -8, -4, -1, 4, 8, 4, 1 }; - static const int8_t dist_y[] = { -4, -1, 4, 8, 4, 1, -4, -8 }; + if (v->type != VEH_ROAD || (v->vehstatus & VS_HIDDEN) == 0) return nullptr; RoadVehFindData *rvf = (RoadVehFindData*)data; + if (abs(v->z_pos - rvf->veh->z_pos) >= 6 || + v->direction != rvf->dir || + rvf->veh->First() == v->First()) return nullptr; + + static const int8_t dist_x[] = { -4, -8, -4, -1, 4, 8, 4, 1 }; + static const int8_t dist_y[] = { -4, -1, 4, 8, 4, 1, -4, -8 }; short x_diff = v->x_pos - rvf->x; short y_diff = v->y_pos - rvf->y; - if (v->type == VEH_ROAD && - !v->IsInDepot() && - abs(v->z_pos - rvf->veh->z_pos) < 6 && - v->direction == rvf->dir && - rvf->veh->First() != v->First() && - (dist_x[v->direction] >= 0 || (x_diff > dist_x[v->direction] && x_diff <= 0)) && - (dist_x[v->direction] <= 0 || (x_diff < dist_x[v->direction] && x_diff >= 0)) && - (dist_y[v->direction] >= 0 || (y_diff > dist_y[v->direction] && y_diff <= 0)) && - (dist_y[v->direction] <= 0 || (y_diff < dist_y[v->direction] && y_diff >= 0))) { - uint diff = abs(x_diff) + abs(y_diff); + /* Check if vehicle is not close. */ + if ((dist_x[v->direction] < 0 && (x_diff > 0 || x_diff <= dist_x[v->direction]))) return nullptr; + if ((dist_x[v->direction] > 0 && (x_diff < 0 || x_diff >= dist_x[v->direction]))) return nullptr; + if ((dist_y[v->direction] < 0 && (y_diff > 0 || y_diff <= dist_y[v->direction]))) return nullptr; + if ((dist_y[v->direction] > 0 && (y_diff < 0 || y_diff >= dist_y[v->direction]))) return nullptr; - if (diff < rvf->best_diff || (diff == rvf->best_diff && v->index < rvf->best->index)) { - rvf->best = v; - rvf->best_diff = diff; - } + uint diff = abs(x_diff) + abs(y_diff); + + if (diff < rvf->best_diff || (diff == rvf->best_diff && v->index < rvf->best->index)) { + rvf->best = v; + rvf->best_diff = diff; } return nullptr; } +/** + * Hide a stopped and visible road vehicle in an extended depot. + * @param v The road vehicle + * @pre v->IsStoppedInDepot() && IsExtendedRoadDepotTile(v->tile) + */ +static void LiftRoadVehicleInDepot(RoadVehicle *v) +{ + assert(v->IsStoppedInDepot()); + assert(IsExtendedRoadDepotTile(v->tile)); + for (RoadVehicle *rv = v; rv != nullptr; rv = rv->Next()) { + rv->vehstatus |= VS_HIDDEN; + rv->tile = v->tile; + rv->direction = v->direction; + rv->x_pos = v->x_pos; + rv->y_pos = v->y_pos; + rv->UpdatePosition(); + rv->Vehicle::UpdateViewport(true); + } + UpdateExtendedDepotReservation(v, false); +} + static RoadVehicle *RoadVehFindCloseTo(RoadVehicle *v, int x, int y, Direction dir, bool update_blocked_ctr = true) { RoadVehFindData rvf; @@ -675,6 +775,17 @@ static RoadVehicle *RoadVehFindCloseTo(RoadVehicle *v, int x, int y, Direction d if (update_blocked_ctr && ++front->blocked_ctr > 1480) return nullptr; + rvf.best = rvf.best->First(); + + /* If the best vehicle is a road vehicle stopped in an extended depot, + * it is in the way of the moving vehicle. Hide the stopped vehicle + * inside the depot. */ + if (rvf.best->IsStoppedInDepot()) { + assert(IsExtendedRoadDepotTile(rvf.best->tile)); + LiftRoadVehicleInDepot(RoadVehicle::From(rvf.best)); + return nullptr; + } + return RoadVehicle::From(rvf.best); } @@ -772,7 +883,7 @@ static Vehicle *EnumFindVehBlockingOvertake(Vehicle *v, void *data) { const OvertakeData *od = (OvertakeData*)data; - return (v->type == VEH_ROAD && v->First() == v && v != od->u && v != od->v) ? v : nullptr; + return (v->type == VEH_ROAD && v->First() == v && v != od->u && v != od->v && ((v->vehstatus & VS_HIDDEN) == 0)) ? v : nullptr; } /** @@ -809,6 +920,9 @@ static void RoadVehCheckOvertake(RoadVehicle *v, RoadVehicle *u) /* Don't overtake in stations */ if (IsTileType(v->tile, MP_STATION) || IsTileType(u->tile, MP_STATION)) return; + /* Don't overtake in road depot platforms. */ + if (IsExtendedRoadDepotTile(v->tile)) return; + /* For now, articulated road vehicles can't overtake anything. */ if (v->HasArticulatedPart()) return; @@ -890,9 +1004,17 @@ static Trackdir RoadFindPathToDest(RoadVehicle *v, TileIndex tile, DiagDirection TrackdirBits trackdirs = TrackStatusToTrackdirBits(ts); if (IsTileType(tile, MP_ROAD)) { - if (IsRoadDepot(tile) && (!IsTileOwner(tile, v->owner) || GetRoadDepotDirection(tile) == enterdir)) { - /* Road depot owned by another company or with the wrong orientation */ - trackdirs = TRACKDIR_BIT_NONE; + if (IsRoadDepot(tile)) { + if (!IsTileOwner(tile, v->owner)) { + trackdirs = TRACKDIR_BIT_NONE; + } else if (IsExtendedRoadDepotTile(tile)) { + if (tile != v->tile) { + RoadBits rb = GetRoadBits(tile, GetRoadTramType(v->roadtype)) & DiagDirToRoadBits(ReverseDiagDir(enterdir)); + if (rb == ROAD_NONE) trackdirs = TRACKDIR_BIT_NONE; + } + } else if (GetRoadDepotDirection(tile) == enterdir) { // Standard depot + trackdirs = TRACKDIR_BIT_NONE; + } } } else if (IsTileType(tile, MP_STATION) && IsBayRoadStopTile(tile)) { /* Standard road stop (drive-through stops are treated as normal road) */ @@ -994,58 +1116,118 @@ found_best_track:; return best_track; } -struct RoadDriveEntry { - uint8_t x, y; -}; +void HandleRoadVehicleEnterDepot(RoadVehicle *v) +{ + assert(IsRoadDepotTile(v->tile)); -#include "table/roadveh_movement.h" + if (IsExtendedRoadDepot(v->tile)) { + assert(v == v->First()); + for (RoadVehicle *u = v; u != nullptr; u = u->Next()) { + assert(u->direction == v->direction); + assert(IsExtendedRoadDepotTile(u->tile)); + assert(GetDepotIndex(u->tile) == GetDepotIndex(v->tile)); + u->state = RVSB_IN_DEPOT; + u->cur_speed = 0; + u->UpdateViewport(true, true); // revise: probably unneded + } + + v->StartService(); + UpdateExtendedDepotReservation(v, true); + + SetWindowClassesDirty(WC_ROADVEH_LIST); + SetWindowDirty(WC_VEHICLE_VIEW, v->index); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); + } else { + VehicleEnterDepot(v); + } +} bool RoadVehLeaveDepot(RoadVehicle *v, bool first) { /* Don't leave unless v and following wagons are in the depot. */ for (const RoadVehicle *u = v; u != nullptr; u = u->Next()) { - if (u->state != RVSB_IN_DEPOT || u->tile != v->tile) return false; + if (!u->IsInDepot()) return false; } - DiagDirection dir = GetRoadDepotDirection(v->tile); - v->direction = DiagDirToDir(dir); + bool visible_vehicle = first && (v->vehstatus & VS_HIDDEN) == 0; - Trackdir tdir = DiagDirToDiagTrackdir(dir); - const RoadDriveEntry *rdp = _road_drive_data[GetRoadTramType(v->roadtype)][(_settings_game.vehicle.road_side << RVS_DRIVE_SIDE) + tdir]; + if (first && (v->vehstatus & VS_HIDDEN) != 0) { + TileIndex new_tile = v->tile; + bool facing_south = IsDiagDirFacingSouth(DirToDiagDir(v->direction)); + if (FindDepotTileForPlacingEngine(new_tile, facing_south, Engine::Get(v->engine_type), true).Failed()) return false; + if (IsExtendedDepot(v->tile)) { + UpdateExtendedDepotReservation(v, false); + v->tile = new_tile; + UpdateExtendedDepotReservation(v, true); + } - int x = TileX(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].x & 0xF); - int y = TileY(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].y & 0xF); + DiagDirection dir = GetRoadDepotExit(v->tile, RoadTramType(v->roadtype), DirToDiagDir(v->direction)); + if (facing_south != IsDiagDirFacingSouth(dir)) dir = ReverseDiagDir(dir); + assert(dir != INVALID_DIAGDIR); + for (Vehicle *u = v; u != nullptr; u = u->Next()) { + u->direction = DiagDirToDir(dir); + u->tile = v->tile; + } + } + + int x = v->x_pos; + int y = v->y_pos; + Trackdir tdir = v->GetVehicleTrackdir(); + + if ((v->vehstatus & VS_HIDDEN) != 0) { + const RoadDriveEntry *rdp = _road_drive_data[GetRoadTramType(v->roadtype)][(_settings_game.vehicle.road_side << RVS_DRIVE_SIDE) + tdir]; + + x = TileX(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].x & 0xF); + y = TileY(v->tile) * TILE_SIZE + (rdp[RVC_DEPOT_START_FRAME].y & 0xF); + } 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) { - VehicleEnterDepot(v); + if (v->current_order.IsType(OT_GOTO_DEPOT) && + IsRoadDepotTile(v->tile) && + v->current_order.GetDestination() == GetDepotIndex(v->tile)) { + if (IsExtendedRoadDepot(v->tile)) { + v->StartService(); + } else { + VehicleEnterDepot(v); + } return true; } - - if (RoadVehFindCloseTo(v, x, y, v->direction, false) != nullptr) return true; - + if ((v->vehstatus & VS_HIDDEN) != 0 && RoadVehFindCloseTo(v, x, y, v->direction, false) != nullptr) return true; VehicleServiceInDepot(v); + v->LeaveUnbunchingDepot(); StartRoadVehSound(v); - - /* Vehicle is about to leave a depot */ + /* Vehicle is about to leave a depot. */ v->cur_speed = 0; } - v->vehstatus &= ~VS_HIDDEN; v->state = tdir; - v->frame = RVC_DEPOT_START_FRAME; - v->x_pos = x; - v->y_pos = y; + if ((v->vehstatus & VS_HIDDEN) != 0) { + v->vehstatus &= ~VS_HIDDEN; + v->x_pos = x; + v->y_pos = y; + v->frame = RVC_DEPOT_START_FRAME; + } else if (v->Next() != nullptr && (v->Next()->vehstatus & VS_HIDDEN) == 0){ + for (RoadVehicle *u = v->Next(); u != nullptr; u = u->Next()) { + u->state = DiagDirToDiagTrackdir(DirToDiagDir(u->direction)); + u->UpdatePosition(); + u->UpdateInclination(true, true); + } + } + v->UpdatePosition(); v->UpdateInclination(true, true); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + if (first && IsExtendedDepot(v->tile)) { + UpdateExtendedDepotReservation(v, false); + } - return true; + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); + + return !visible_vehicle; } static Trackdir FollowPreviousRoadVehicle(const RoadVehicle *v, const RoadVehicle *prev, TileIndex tile, DiagDirection entry_dir, bool already_reversed) @@ -1065,7 +1247,7 @@ static Trackdir FollowPreviousRoadVehicle(const RoadVehicle *v, const RoadVehicl if (IsTileType(tile, MP_TUNNELBRIDGE)) { diag_dir = GetTunnelBridgeDirection(tile); } else if (IsRoadDepotTile(tile)) { - diag_dir = ReverseDiagDir(GetRoadDepotDirection(tile)); + diag_dir = ReverseDiagDir(IsExtendedRoadDepot(tile) ? DirToDiagDir(v->direction) : GetRoadDepotDirection(tile)); } if (diag_dir == INVALID_DIAGDIR) return INVALID_TRACKDIR; @@ -1133,6 +1315,53 @@ static bool CanBuildTramTrackOnTile(CompanyID c, TileIndex t, RoadType rt, RoadB return ret.Succeeded(); } +/** Check whether there is a close vehicle ahead and act as needed. + * @param v Moving vehicle + * @param x x coordinate to check + * @param y y coordinate to check + * @param dir direction of the vehicle + * @return whether a close vehicle is found. + */ +bool CheckCloseVehicle(RoadVehicle *v, int x, int y, Direction dir) +{ + if (!v->IsFrontEngine() || IsInsideMM(v->state, RVSB_IN_ROAD_STOP, RVSB_IN_ROAD_STOP_END)) return false; + /* Vehicle is not in a road stop. + * Check for another vehicle to overtake */ + RoadVehicle *u = RoadVehFindCloseTo(v, x, y, dir); + + if (u == nullptr) return false; + assert(u == u->First()); + + /* There is a vehicle in front overtake it if possible */ + if (v->overtaking == 0) RoadVehCheckOvertake(v, u); + if (v->overtaking == 0) v->cur_speed = u->cur_speed; + + /* In case we are in a road depot platform, why not try to start servicing? */ + if (IsExtendedRoadDepotTile(v->tile) && v->current_order.IsType(OT_GOTO_DEPOT)) { + DepotID depot_id = GetDepotIndex(v->tile); + if (v->current_order.GetDestination() != depot_id) return true; + if (!u->IsInDepot() || GetDepotIndex(u->tile) != depot_id) return true; + for (u = v; u != nullptr; u = u->Next()) { + if (!IsExtendedRoadDepotTile(u->tile) || GetDepotIndex(u->tile) != depot_id) return true; + if (v->direction != u->direction) return true; + } + HandleRoadVehicleEnterDepot(v); + return true; + } + + /* In case an RV is stopped in a road stop, why not try to load? */ + if (v->cur_speed == 0 && IsInsideMM(v->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END) && + v->current_order.ShouldStopAtStation(v, GetStationIndex(v->tile)) && + v->owner == GetTileOwner(v->tile) && !v->current_order.IsType(OT_LEAVESTATION) && + GetRoadStopType(v->tile) == (v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK)) { + Station *st = Station::GetByTile(v->tile); + v->last_station_visited = st->index; + RoadVehArrivesAt(v, st); + v->BeginLoading(); + } + return true; +} + bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) { if (v->overtaking != 0) { @@ -1161,7 +1390,8 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) if (v->IsFrontEngine()) { const Vehicle *u = RoadVehFindCloseTo(v, gp.x, gp.y, v->direction); if (u != nullptr) { - v->cur_speed = u->First()->cur_speed; + assert(u == u->First()); + v->cur_speed = u->cur_speed; return false; } } @@ -1190,18 +1420,21 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) (_settings_game.vehicle.road_side << RVS_DRIVE_SIDE)) ^ v->overtaking][v->frame + 1]; if (rd.x & RDE_NEXT_TILE) { - TileIndex tile = v->tile + TileOffsByDiagDir((DiagDirection)(rd.x & 3)); + DiagDirection diag_dir = (DiagDirection)(rd.x & 3); + TileIndex tile = v->tile + TileOffsByDiagDir(diag_dir); Trackdir dir; + bool extended_depot_turn = IsExtendedRoadDepotTile(v->tile) && + (GetRoadBits(v->tile, GetRoadTramType(v->roadtype)) & DiagDirToRoadBits(diag_dir)) == ROAD_NONE; if (v->IsFrontEngine()) { /* If this is the front engine, look for the right path. */ - if (HasTileAnyRoadType(tile, v->compatible_roadtypes)) { - dir = RoadFindPathToDest(v, tile, (DiagDirection)(rd.x & 3)); + if (HasTileAnyRoadType(tile, v->compatible_roadtypes) && !extended_depot_turn) { + dir = RoadFindPathToDest(v, tile, diag_dir); } else { - dir = _road_reverse_table[(DiagDirection)(rd.x & 3)]; + dir = _road_reverse_table[diag_dir]; } } else { - dir = FollowPreviousRoadVehicle(v, prev, tile, (DiagDirection)(rd.x & 3), false); + dir = FollowPreviousRoadVehicle(v, prev, tile, diag_dir, false); } if (dir == INVALID_TRACKDIR) { @@ -1228,7 +1461,10 @@ again: case TRACKDIR_RVREV_SW: needed = ROAD_NE; break; case TRACKDIR_RVREV_NW: needed = ROAD_SE; break; } - if ((v->Previous() != nullptr && v->Previous()->tile == tile) || + if (extended_depot_turn) { + tile = v->tile; + start_frame = RVC_TURN_AROUND_START_FRAME_SHORT_TRAM; + } else if ((v->Previous() != nullptr && v->Previous()->tile == tile) || (v->IsFrontEngine() && IsNormalRoadTile(tile) && !HasRoadWorks(tile) && HasTileAnyRoadType(tile, v->compatible_roadtypes) && (needed & GetRoadBits(tile, RTT_TRAM)) != ROAD_NONE)) { @@ -1245,8 +1481,9 @@ again: } else if (!v->IsFrontEngine() || !CanBuildTramTrackOnTile(v->owner, tile, v->roadtype, needed) || ((~needed & GetAnyRoadBits(v->tile, RTT_TRAM, false)) == ROAD_NONE)) { /* * Taking the 'small' corner for trams only happens when: - * - We are not the from vehicle of an articulated tram. + * - We are not the front vehicle of an articulated tram. * - Or when the company cannot build on the next tile. + * - Or when the extended depot doesn't have the appropriate tram bit to continue. * * The 'small' corner means that the vehicle is on the end of a * tram track and needs to start turning there. To do this properly @@ -1279,7 +1516,8 @@ again: if (v->IsFrontEngine()) { const Vehicle *u = RoadVehFindCloseTo(v, x, y, new_dir); if (u != nullptr) { - v->cur_speed = u->First()->cur_speed; + assert(u == u->First()); + v->cur_speed = u->cur_speed; /* We might be blocked, prevent pathfinding rerun as we already know where we are heading to. */ v->path.tile.push_front(tile); v->path.td.push_front(dir); @@ -1288,6 +1526,11 @@ again: } uint32_t r = VehicleEnterTile(v, tile, x, y); + if (HasBit(r, VETS_ENTERED_DEPOT_PLATFORM) && v->Next() == nullptr && v == v->First()) { + HandleRoadVehicleEnterDepot(RoadVehicle::From(v)); + return false; + } + if (HasBit(r, VETS_CANNOT_ENTER)) { if (!IsTileType(tile, MP_TUNNELBRIDGE)) { v->cur_speed = 0; @@ -1344,6 +1587,9 @@ again: } v->x_pos = x; v->y_pos = y; + if (prev != nullptr && prev->IsInDepot() && (prev->vehstatus & VS_HIDDEN) == 0) { + v->state = RVSB_IN_DEPOT; + } v->UpdatePosition(); RoadZPosAffectSpeed(v, v->UpdateInclination(true, true)); return true; @@ -1354,7 +1600,7 @@ again: Trackdir dir; uint turn_around_start_frame = RVC_TURN_AROUND_START_FRAME; - if (RoadTypeIsTram(v->roadtype) && !IsRoadDepotTile(v->tile) && HasExactlyOneBit(GetAnyRoadBits(v->tile, RTT_TRAM, true))) { + if (RoadTypeIsTram(v->roadtype) && !IsRoadDepotTile(v->tile) && !IsExtendedRoadDepotTile(v->tile) && HasExactlyOneBit(GetAnyRoadBits(v->tile, RTT_TRAM, true))) { /* * The tram is turning around with one tram 'roadbit'. This means that * it is using the 'big' corner 'drive data'. However, to support the @@ -1395,7 +1641,8 @@ again: if (v->IsFrontEngine()) { const Vehicle *u = RoadVehFindCloseTo(v, x, y, new_dir); if (u != nullptr) { - v->cur_speed = u->First()->cur_speed; + assert(u == u->First()); + v->cur_speed = u->cur_speed; /* We might be blocked, prevent pathfinding rerun as we already know where we are heading to. */ v->path.tile.push_front(v->tile); v->path.td.push_front(dir); @@ -1419,6 +1666,9 @@ again: v->x_pos = x; v->y_pos = y; + if (prev != nullptr && prev->IsInDepot() && (prev->vehstatus & VS_HIDDEN) == 0) { + v->state = RVSB_IN_DEPOT; + } v->UpdatePosition(); RoadZPosAffectSpeed(v, v->UpdateInclination(true, true)); return true; @@ -1428,8 +1678,10 @@ again: * it's on a depot tile, check if it's time to activate the next vehicle in * the chain yet. */ if (v->Next() != nullptr && IsRoadDepotTile(v->tile)) { - if (v->frame == v->gcache.cached_veh_length + RVC_DEPOT_START_FRAME) { - RoadVehLeaveDepot(v->Next(), false); + if ((v->Next()->vehstatus & VS_HIDDEN)) { + if (v->frame == v->gcache.cached_veh_length + RVC_DEPOT_START_FRAME) { + RoadVehLeaveDepot(v->Next(), false); + } } } @@ -1439,30 +1691,7 @@ again: Direction new_dir = RoadVehGetSlidingDirection(v, x, y); - if (v->IsFrontEngine() && !IsInsideMM(v->state, RVSB_IN_ROAD_STOP, RVSB_IN_ROAD_STOP_END)) { - /* Vehicle is not in a road stop. - * Check for another vehicle to overtake */ - RoadVehicle *u = RoadVehFindCloseTo(v, x, y, new_dir); - - if (u != nullptr) { - u = u->First(); - /* There is a vehicle in front overtake it if possible */ - if (v->overtaking == 0) RoadVehCheckOvertake(v, u); - if (v->overtaking == 0) v->cur_speed = u->cur_speed; - - /* In case an RV is stopped in a road stop, why not try to load? */ - if (v->cur_speed == 0 && IsInsideMM(v->state, RVSB_IN_DT_ROAD_STOP, RVSB_IN_DT_ROAD_STOP_END) && - v->current_order.ShouldStopAtStation(v, GetStationIndex(v->tile)) && - v->owner == GetTileOwner(v->tile) && !v->current_order.IsType(OT_LEAVESTATION) && - GetRoadStopType(v->tile) == (v->IsBus() ? ROADSTOP_BUS : ROADSTOP_TRUCK)) { - Station *st = Station::GetByTile(v->tile); - v->last_station_visited = st->index; - RoadVehArrivesAt(v, st); - v->BeginLoading(); - } - return false; - } - } + if (CheckCloseVehicle(v, x, y, new_dir)) return false; Direction old_dir = v->direction; if (new_dir != old_dir) { @@ -1558,6 +1787,16 @@ again: v->y_pos = y; v->UpdatePosition(); RoadZPosAffectSpeed(v, v->UpdateInclination(false, true)); + + /* After updating the position, check whether the vehicle can stop in a depot platform. */ + if (IsExtendedRoadDepotTile(v->tile) && v->Next() == nullptr) { + RoadVehicle *first = RoadVehicle::From(v)->First(); + if (HasBit(VehicleEnterTile(first, first->tile, first->x_pos, first->y_pos), VETS_ENTERED_DEPOT_PLATFORM)) { + HandleRoadVehicleEnterDepot(first); + return false; + } + } + return true; } @@ -1579,6 +1818,8 @@ static bool RoadVehController(RoadVehicle *v) return true; } + if (v->ContinueServicing()) return true; + ProcessOrders(v); v->HandleLoading(); @@ -1740,6 +1981,9 @@ Trackdir RoadVehicle::GetVehicleTrackdir() const if (this->vehstatus & VS_CRASHED) return INVALID_TRACKDIR; if (this->IsInDepot()) { + if (IsExtendedRoadDepot(this->tile)) { + return DiagDirToDiagTrackdir(DirToDiagDir(this->direction)); + } /* We'll assume the road vehicle is facing outwards */ return DiagDirToDiagTrackdir(GetRoadDepotDirection(this->tile)); } diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 06102930b7..6afc3b3861 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -11,6 +11,7 @@ #include "../void_map.h" #include "../signs_base.h" #include "../depot_base.h" +#include "../depot_func.h" #include "../fios.h" #include "../gamelog_internal.h" #include "../network/network.h" @@ -222,6 +223,7 @@ static inline RailType UpdateRailType(RailType rt, RailType min) void UpdateAllVirtCoords() { UpdateAllStationVirtCoords(); + UpdateAllDepotVirtCoords(); UpdateAllSignVirtCoords(); UpdateAllTownVirtCoords(); UpdateAllTextEffectVirtCoords(); @@ -294,6 +296,10 @@ static void InitializeWindowsAndCaches() } } + for (Depot *dep : Depot::Iterate()) { + dep->RescanDepotTiles(); + } + RecomputePrices(); GroupStatistics::UpdateAfterLoad(); @@ -641,6 +647,15 @@ bool AfterLoadGame() } } + if (IsSavegameVersionBefore(SLV_DEPOTS_ALIGN_RAIL_DEPOT_BITS)) { + for (auto t : Map::Iterate()) { + if (IsTileType(t, MP_RAILWAY) && GetRailTileType(t) == 3) { + /* Change the rail type for depots from old value 3 to new value 2. */ + SB(t.m5(), 6, 2, RAIL_TILE_DEPOT); + } + } + } + /* in version 2.1 of the savegame, town owner was unified. */ if (IsSavegameVersionBefore(SLV_2, 1)) ConvertTownOwner(); @@ -795,6 +810,24 @@ bool AfterLoadGame() _settings_game.linkgraph.recalc_time *= CalendarTime::SECONDS_PER_DAY; } + if (IsSavegameVersionBefore(SLV_DEPOT_SPREAD)) { + _settings_game.depot.depot_spread = 1; + _settings_game.depot.adjacent_depots = true; + _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; + } + + if (IsSavegameVersionBefore(SLV_EXTENDED_DEPOTS)) { + /* Set standard depots as the only available depots. */ + _settings_game.depot.rail_depot_types = 1; + _settings_game.depot.road_depot_types = 1; + _settings_game.depot.water_depot_types = 1; + } + /* Load the sprites */ GfxLoadSprites(); LoadStringWidthTable(); @@ -2425,28 +2458,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 +2803,112 @@ 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; + depot->Disuse(); + 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; + } + } + + for (auto t : Map::Iterate()) { + if (!IsRoadDepotTile(t)) continue; + DiagDirection dir = (DiagDirection)GB(t.m5(), 0, 2); + SB(t.m5(), 0, 6, 0); + RoadBits rb = DiagDirToRoadBits(dir); + SetRoadBits(t, rb, HasRoadTypeRoad(t) ? RTT_ROAD : RTT_TRAM); + SB(t.m6(), 6, 2, dir); + } + } + + /* 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/company_sl.cpp b/src/saveload/company_sl.cpp index 8e49af0eed..69d8cc515b 100644 --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -134,8 +134,8 @@ void AfterLoadCompanyStats() RoadType rt = GetRoadType(tile, rtt); if (rt == INVALID_ROADTYPE) continue; c = Company::GetIfValid(IsRoadDepot(tile) ? GetTileOwner(tile) : GetRoadOwner(tile, rtt)); - /* A level crossings and depots have two road bits. */ - if (c != nullptr) c->infrastructure.road[rt] += IsNormalRoad(tile) ? CountBits(GetRoadBits(tile, rtt)) : 2; + /* Level crossings have two road bits. */ + if (c != nullptr) c->infrastructure.road[rt] += (IsNormalRoad(tile) || IsRoadDepot(tile)) ? CountBits(GetRoadBits(tile, rtt)) : 2; } break; } 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..4bc5dde166 100644 --- a/src/saveload/depot_sl.cpp +++ b/src/saveload/depot_sl.cpp @@ -27,6 +27,13 @@ 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), + SLE_CONDVAR(Depot, delete_ctr, SLE_UINT8, SLV_KEEP_REMOVED_DEPOTS, SL_MAX_VERSION), }; struct DEPTChunkHandler : ChunkHandler { diff --git a/src/saveload/map_sl.cpp b/src/saveload/map_sl.cpp index 3ce23a42b2..8d13947bc0 100644 --- a/src/saveload/map_sl.cpp +++ b/src/saveload/map_sl.cpp @@ -15,6 +15,7 @@ #include "../map_func.h" #include "../core/bitmath_func.hpp" #include "../fios.h" +#include "../tile_map.h" #include "../safeguards.h" @@ -243,6 +244,16 @@ struct MAP5ChunkHandler : ChunkHandler { SlCopy(buf.data(), MAP_SL_BUF_SIZE, SLE_UINT8); for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) Tile(i++).m5() = buf[j]; } + + if (IsSavegameVersionBefore(SLV_ALIGN_WATER_BITS)) { + /* Move some bits for alignment purposes. */ + for (TileIndex i = 0; i != size; i++) { + if (IsTileType(i, MP_WATER)) { + SB(Tile(i).m5(), 6, 1, GB(Tile(i).m5(), 4, 1)); + SB(Tile(i).m5(), 4, 1, 0); + } + } + } } void Save() const override 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..857817d10d 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -383,7 +383,24 @@ 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, ///< XXX PR#10691 Add depots to airports that have a hangar. + SLV_DEPOTID_IN_HANGAR_ORDERS, ///< 320 PR#10691 Go to hangar orders store the DepotID instead of StationID. + + SLV_DEPOTID_BACKUP_ORDERS, ///< 314 PR#XXXXX Backup orders are indexed through DepotIDs. + + SLV_ALIGN_WATER_BITS, ///< 315 PR#XXXXX Align some water bits in the map array. + SLV_DEPOTS_ALIGN_RAIL_DEPOT_BITS, ///< 316 PR#XXXXX Align one bit for rail depots. + SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, ///< 317 PR#XXXXX Add some members to depot struct. + SLV_DEPOT_SPREAD, ///< 318 PR#XXXXX Add a setting for max depot spread. + SLV_ALLOW_INCOMPATIBLE_REPLACEMENTS, ///< 319 PR#XXXXX Allow incompatible vehicle replacements. + + SLV_KEEP_REMOVED_DEPOTS, ///< 320 PR#XXXXX Keep remove depots for a while. + + SLV_EXTENDED_DEPOTS, ///< 321 PR#8480 Extended depots for rail, road and water transport. + + SLV_PATCHED = UINT16_MAX - 6, ///< Make it difficult to load any savegame made with + // this patched version in any other version of OpenTTD (unless it uses the same saveload version trick). SL_MAX_VERSION, ///< Highest possible saveload version }; @@ -598,6 +615,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/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index 4e42de3962..f28d04b0e5 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -665,6 +665,7 @@ public: SLE_VAR(Vehicle, progress, SLE_UINT8), SLE_VAR(Vehicle, vehstatus, SLE_UINT8), + SLE_CONDVAR(Vehicle, wait_counter, SLE_UINT16, SLV_EXTENDED_DEPOTS, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, last_station_visited, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_5), SLE_CONDVAR(Vehicle, last_station_visited, SLE_UINT16, SLV_5, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, last_loading_station, SLE_UINT16, SLV_182, SL_MAX_VERSION), @@ -793,7 +794,7 @@ public: SLE_CONDVAR(Train, flags, SLE_FILE_U8 | SLE_VAR_U16, SLV_2, SLV_100), SLE_CONDVAR(Train, flags, SLE_UINT16, SLV_100, SL_MAX_VERSION), - SLE_CONDVAR(Train, wait_counter, SLE_UINT16, SLV_136, SL_MAX_VERSION), + SLE_CONDVAR(Train, wait_counter, SLE_UINT16, SLV_136, SLV_EXTENDED_DEPOTS), SLE_CONDVAR(Train, gv_flags, SLE_UINT16, SLV_139, SL_MAX_VERSION), }; inline const static SaveLoadCompatTable compat_description = _vehicle_train_sl_compat; @@ -1072,6 +1073,7 @@ struct VEHSChunkHandler : ChunkHandler { default: SlErrorCorrupt("Invalid vehicle type"); } + if (IsSavegameVersionBefore(SLV_EXTENDED_DEPOTS)) assert(v->type == VEH_TRAIN || v->wait_counter == 0); SlObject(v, slt); if (_cargo_count != 0 && IsCompanyBuildableVehicleType(v) && CargoPacket::CanAllocateItem()) { diff --git a/src/script/api/script_depotlist.cpp b/src/script/api/script_depotlist.cpp index eb43139165..0906a86da9 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->IsInUse() || 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..5b30290d4b 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, 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..b11f19d03b 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: { @@ -490,11 +485,10 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr * to a depot (other vehicle types). */ if (::Vehicle::Get(vehicle_id)->type == VEH_AIRCRAFT) { if (!::IsTileType(destination, MP_STATION)) return false; - order.MakeGoToDepot(::GetStationIndex(destination), odtf, onsf, odaf); } else { if (::IsTileType(destination, MP_STATION)) return false; - order.MakeGoToDepot(::GetDepotIndex(destination), odtf, onsf, odaf); } + order.MakeGoToDepot(::GetDepotIndex(destination), odtf, onsf, odaf); } break; } diff --git a/src/script/api/script_rail.cpp b/src/script/api/script_rail.cpp index ac1b9fc5a9..d92010de7f 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, 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..73c6fa5da7 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, false, false, 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/script/api/script_vehiclelist.cpp b/src/script/api/script_vehiclelist.cpp index 73bb4f4d30..267dac241a 100644 --- a/src/script/api/script_vehiclelist.cpp +++ b/src/script/api/script_vehiclelist.cpp @@ -60,7 +60,7 @@ ScriptVehicleList_Depot::ScriptVehicleList_Depot(TileIndex tile) case MP_STATION: // Aircraft if (!IsAirport(tile)) return; type = VEH_AIRCRAFT; - dest = GetStationIndex(tile); + dest = GetDepotIndex(tile); break; case MP_RAILWAY: diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index fb5c08ec86..9a24e44b47 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -2145,6 +2145,19 @@ 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.rail_depot_types")); + depots->Add(new SettingEntry("depot.road_depot_types")); + depots->Add(new SettingEntry("depot.water_depot_types")); + + 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 0fd874a38c..72100e8f6b 100644 --- a/src/settings_table.cpp +++ b/src/settings_table.cpp @@ -380,6 +380,27 @@ 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); +} + +static void DepotSettingsChanged(int32_t) +{ + CloseWindowByClass(WC_BUILD_TOOLBAR); +} + /** * 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..7caa7cd30f 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -570,6 +570,26 @@ struct StationSettings { uint8_t station_spread; ///< amount a station may spread }; +enum DepotTypes : uint8_t { + ONLY_STANDARD_DEPOT_TYPE = 1, + ONLY_EXTENDED_DEPOT_TYPE = 2, + BOTH_DEPOT_TYPES = 3, +}; + +/** Settings related to depots. */ +struct DepotSettings { + uint8_t depot_spread; ///< amount a depot may spread + bool adjacent_depots; ///< allow depots to be built directly adjacent to other depots + bool distant_join_depots; ///< allow to join non-adjacent depots + + uint8_t rail_depot_types; ///< allowed rail depot types for contruction + uint8_t road_depot_types; ///< allowed road depot types for contruction + uint8_t water_depot_types; ///< allowed water depot types for contruction + + 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 +623,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.h b/src/ship.h index c47be05436..42d9392e88 100644 --- a/src/ship.h +++ b/src/ship.h @@ -31,7 +31,7 @@ struct Ship final : public SpecializedVehicle { /** We don't want GCC to zero our struct! It already is zeroed and has an index! */ Ship() : SpecializedVehicleBase() {} /** We want to 'destruct' the right class. */ - virtual ~Ship() { this->PreDestructor(); } + virtual ~Ship(); void MarkDirty() override; void UpdateDeltaXY() override; @@ -43,7 +43,7 @@ struct Ship final : public SpecializedVehicle { int GetDisplayMaxSpeed() const override { return this->vcache.cached_max_speed / 2; } int GetCurrentMaxSpeed() const override { return std::min(this->vcache.cached_max_speed, this->current_order.GetMaxSpeed() * 2); } Money GetRunningCost() const override; - bool IsInDepot() const override { return this->state == TRACK_BIT_DEPOT; } + bool IsInDepot() const override { return HasBit((uint8_t)this->state, TRACK_DEPOT); } bool Tick() override; void OnNewCalendarDay() override; void OnNewEconomyDay() override; diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index dcd2f6825f..40c49152cf 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -36,6 +36,7 @@ #include "industry.h" #include "industry_map.h" #include "ship_cmd.h" +#include "command_func.h" #include "table/strings.h" @@ -187,14 +188,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 || !depot->IsInUse()) 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 +223,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); } @@ -290,11 +291,12 @@ Trackdir Ship::GetVehicleTrackdir() const if (this->vehstatus & VS_CRASHED) return INVALID_TRACKDIR; if (this->IsInDepot()) { - /* We'll assume the ship is facing outwards */ - return DiagDirToDiagTrackdir(GetShipDepotDirection(this->tile)); + /* Only old depots need it. */ + /* We'll assume the ship is facing outwards. */ + if (this->state == TRACK_BIT_DEPOT) return DiagDirToDiagTrackdir(GetShipDepotDirection(this->tile)); } - if (this->state == TRACK_BIT_WORMHOLE) { + if (this->state == TRACK_BIT_WORMHOLE || this->IsInDepot()) { /* ship on aqueduct, so just use its direction and assume a diagonal track */ return DiagDirToDiagTrackdir(DirToDiagDir(this->direction)); } @@ -302,6 +304,15 @@ Trackdir Ship::GetVehicleTrackdir() const return TrackDirectionToTrackdir(FindFirstTrack(this->state), this->direction); } +Ship::~Ship() +{ + if (CleaningPool()) return; + + if (this->IsInDepot()) SetDepotReservation(this->tile, DEPOT_RESERVATION_EMPTY); + + this->PreDestructor(); +} + void Ship::MarkDirty() { this->colourmap = PAL_NONE; @@ -373,6 +384,32 @@ static bool CheckReverseShip(const Ship *v, Trackdir *trackdir = nullptr) return YapfShipCheckReverse(v, trackdir); } +static bool CheckPlaceShipOnDepot(TileIndex tile) +{ + assert(IsShipDepotTile(tile)); + return !IsExtendedDepot(tile) || IsExtendedDepotEmpty(tile); +} + +void HandleShipEnterDepot(Ship *v) +{ + assert(IsShipDepotTile(v->tile)); + + if (IsExtendedDepot(v->tile)) { + SetDepotReservation(v->tile, DEPOT_RESERVATION_IN_USE); + v->state |= TRACK_BIT_DEPOT; + v->cur_speed = 0; + v->UpdateCache(); + v->UpdateViewport(true, true); + SetWindowClassesDirty(WC_SHIPS_LIST); + SetWindowDirty(WC_VEHICLE_VIEW, v->index); + + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); + v->StartService(); + } else { + VehicleEnterDepot(v); + } +} + static bool CheckShipLeaveDepot(Ship *v) { if (!v->IsChainInDepot()) return false; @@ -383,53 +420,66 @@ static bool CheckShipLeaveDepot(Ship *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) && IsShipDepotTile(v->tile) && GetDepotIndex(v->tile) == v->current_order.GetDestination()) { - VehicleEnterDepot(v); + HandleShipEnterDepot(v); return true; } /* Don't leave depot if no destination set */ if (v->dest_tile == 0) return true; - /* Don't leave depot if another vehicle is already entering/leaving */ - /* This helps avoid CPU load if many ships are set to start at the same time */ - if (HasVehicleOnPos(v->tile, nullptr, &EnsureNoMovingShipProc)) return true; - - TileIndex tile = v->tile; - Axis axis = GetShipDepotAxis(tile); - - DiagDirection north_dir = ReverseDiagDir(AxisToDiagDir(axis)); - TileIndex north_neighbour = TileAdd(tile, TileOffsByDiagDir(north_dir)); - DiagDirection south_dir = AxisToDiagDir(axis); - TileIndex south_neighbour = TileAdd(tile, 2 * TileOffsByDiagDir(south_dir)); - - TrackBits north_tracks = DiagdirReachesTracks(north_dir) & GetTileShipTrackStatus(north_neighbour); - TrackBits south_tracks = DiagdirReachesTracks(south_dir) & GetTileShipTrackStatus(south_neighbour); - if (north_tracks && south_tracks) { - if (CheckReverseShip(v)) north_tracks = TRACK_BIT_NONE; - } - - if (north_tracks) { - /* Leave towards north */ - v->rotation = v->direction = DiagDirToDir(north_dir); - } else if (south_tracks) { - /* Leave towards south */ - v->rotation = v->direction = DiagDirToDir(south_dir); + if (IsExtendedDepot(v->tile)) { + SetDepotReservation(v->tile, DEPOT_RESERVATION_EMPTY); } else { - /* Both ways blocked */ - return false; + /* Don't leave depot if another vehicle is already entering/leaving */ + /* This helps avoid CPU load if many ships are set to start at the same time */ + if (HasVehicleOnPos(v->tile, nullptr, &EnsureNoMovingShipProc)) return true; + + TileIndex tile = v->tile; + Axis axis = GetShipDepotAxis(tile); + bool reverse = false; + + DiagDirection north_dir = ReverseDiagDir(AxisToDiagDir(axis)); + TileIndex north_neighbour = TileAdd(tile, TileOffsByDiagDir(north_dir)); + DiagDirection south_dir = AxisToDiagDir(axis); + TileIndex south_neighbour = TileAdd(tile, 2 * TileOffsByDiagDir(south_dir)); + + TrackBits north_tracks = DiagdirReachesTracks(north_dir) & GetTileShipTrackStatus(north_neighbour); + TrackBits south_tracks = DiagdirReachesTracks(south_dir) & GetTileShipTrackStatus(south_neighbour); + if (north_tracks && south_tracks) { + if (CheckReverseShip(v)) north_tracks = TRACK_BIT_NONE; + } + + if (north_tracks) { + /* Leave towards north */ + v->rotation = v->direction = DiagDirToDir(north_dir); + } else if (south_tracks) { + /* Leave towards south */ + v->rotation = v->direction = DiagDirToDir(south_dir); + } else { + /* Both ways blocked */ + return false; + } + + v->state = AxisToTrackBits(axis); + v->vehstatus &= ~VS_HIDDEN; + + /* Leave towards south if reverse. */ + v->rotation = v->direction = DiagDirToDir(reverse ? south_dir : north_dir); + + v->state = AxisToTrackBits(axis); + v->vehstatus &= ~VS_HIDDEN; } - v->state = AxisToTrackBits(axis); - v->vehstatus &= ~VS_HIDDEN; - + v->state &= ~TRACK_BIT_DEPOT; 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; @@ -494,6 +544,13 @@ static void ShipArrivesAt(const Vehicle *v, Station *st) */ static Track ChooseShipTrack(Ship *v, TileIndex tile, TrackBits tracks) { + /* Before choosing a track, if close to the destination station or depot (not an oil rig)... */ + if (DistanceManhattan(v->dest_tile, tile) <= 5 && (v->current_order.IsType(OT_GOTO_DEPOT) && + (!IsShipDepotTile(v->dest_tile) || (IsExtendedDepotTile(v->dest_tile) && !IsExtendedDepotEmpty(v->dest_tile))))) { + /* Try to get a depot tile. */ + v->dest_tile = Depot::Get(v->current_order.GetDestination())->GetBestDepotTile(v); + } + bool path_found = true; Track track; @@ -705,6 +762,8 @@ static void ShipController(Ship *v) if (v->vehstatus & VS_STOPPED) return; + if (v->ContinueServicing()) return; + if (ProcessOrders(v) && CheckReverseShip(v)) return ReverseShip(v); v->HandleLoading(); @@ -764,13 +823,6 @@ static void ShipController(Ship *v) UpdateVehicleTimetable(v, true); v->IncrementRealOrderIndex(); v->current_order.MakeDummy(); - } else if (v->current_order.IsType(OT_GOTO_DEPOT) && - v->dest_tile == gp.new_tile) { - /* Depot orders really need to reach the tile */ - if ((gp.x & 0xF) == 8 && (gp.y & 0xF) == 8) { - VehicleEnterDepot(v); - return; - } } else if (v->current_order.IsType(OT_GOTO_STATION) && IsDockingTile(gp.new_tile)) { /* Process station in the orderlist. */ Station *st = Station::Get(v->current_order.GetDestination()); @@ -791,6 +843,18 @@ static void ShipController(Ship *v) /* New tile */ if (!IsValidTile(gp.new_tile)) return ReverseShip(v); + if (v->current_order.IsType(OT_GOTO_DEPOT) && + IsShipDepotTile(gp.new_tile) && + GetOtherShipDepotTile(gp.new_tile) == gp.old_tile && + v->current_order.GetDestination() == GetDepotIndex(gp.new_tile)) { + if (CheckPlaceShipOnDepot(v->tile)) { + HandleShipEnterDepot(v); + v->UpdatePosition(); + v->UpdateViewport(true, true); + return; + } + } + const DiagDirection diagdir = DiagdirBetweenTiles(gp.old_tile, gp.new_tile); assert(diagdir != INVALID_DIAGDIR); const TrackBits tracks = GetAvailShipTracks(gp.new_tile, diagdir); @@ -894,10 +958,22 @@ void Ship::SetDestTile(TileIndex tile) */ CommandCost CmdBuildShip(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **ret) { - tile = GetShipDepotNorthTile(tile); + assert(IsShipDepotTile(tile)); + if (!(flags & DC_AUTOREPLACE)) { + std::vector *depot_tiles = &(Depot::GetByTile(tile)->depot_tiles); + tile = INVALID_TILE; + for (std::vector::iterator it = depot_tiles->begin(); it != depot_tiles->end(); ++it) { + if (CheckPlaceShipOnDepot(*it)) { + tile = *it; + break; + } + } + if (tile == INVALID_TILE) return_cmd_error(STR_ERROR_NO_FREE_DEPOT); + } + if (flags & DC_EXEC) { - int x; - int y; + bool is_extended_depot = IsExtendedDepot(tile); + TileIndexDiffC offset = TileIndexDiffCByDiagDir(ReverseDiagDir(GetShipDepotDirection(tile))); const ShipVehicleInfo *svi = &e->u.ship; @@ -906,14 +982,22 @@ CommandCost CmdBuildShip(DoCommandFlag flags, TileIndex tile, const Engine *e, V v->owner = _current_company; v->tile = tile; - x = TileX(tile) * TILE_SIZE + TILE_SIZE / 2; - y = TileY(tile) * TILE_SIZE + TILE_SIZE / 2; - v->x_pos = x; - v->y_pos = y; - v->z_pos = GetSlopePixelZ(x, y); + v->x_pos = TileX(tile) * TILE_SIZE + TILE_SIZE / 2 + offset.x * (TILE_SIZE / 2 - 1); + v->y_pos = TileY(tile) * TILE_SIZE + TILE_SIZE / 2 + offset.y * (TILE_SIZE / 2 - 1); + v->z_pos = GetSlopePixelZ(v->x_pos, v->y_pos); + v->state = TRACK_BIT_DEPOT; + if (is_extended_depot) { + v->state |= AxisToTrackBits(GetShipDepotAxis(tile)); + v->direction = AxisToDirection(GetShipDepotAxis(v->tile)); + SetDepotReservation(v->tile, DEPOT_RESERVATION_FULL_STOPPED_VEH); + } else { + v->vehstatus |= VS_HIDDEN; + } + + v->rotation = v->direction; v->UpdateDeltaXY(); - v->vehstatus = VS_HIDDEN | VS_STOPPED | VS_DEFPAL; + v->vehstatus |= VS_STOPPED | VS_DEFPAL; v->spritenum = svi->image_index; v->cargo_type = e->GetDefaultCargoType(); @@ -929,8 +1013,6 @@ CommandCost CmdBuildShip(DoCommandFlag flags, TileIndex tile, const Engine *e, V v->reliability_spd_dec = e->reliability_spd_dec; v->max_age = e->GetLifeLengthInDays(); - v->state = TRACK_BIT_DEPOT; - v->SetServiceInterval(Company::Get(_current_company)->settings.vehicle.servint_ships); v->date_of_last_service = TimerGameEconomy::date; v->date_of_last_service_newgrf = TimerGameCalendar::date; @@ -951,6 +1033,8 @@ CommandCost CmdBuildShip(DoCommandFlag flags, TileIndex tile, const Engine *e, V v->InvalidateNewGRFCacheOfChain(); v->UpdatePosition(); + + if (is_extended_depot) v->MarkDirty(); } return CommandCost(); @@ -961,5 +1045,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/signal.cpp b/src/signal.cpp index d060c58d22..e7ef7f7b5b 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -278,6 +278,13 @@ static SigFlags ExploreSegment(Owner owner) if (GetTileOwner(tile) != owner) continue; // do not propagate signals on others' tiles (remove for tracksharing) if (IsRailDepot(tile)) { + if (IsExtendedRailDepot(tile)) { + assert(enterdir != INVALID_DIAGDIR); + if (DiagDirToDiagTrack(enterdir) != GetRailDepotTrack(tile)) continue; // different axis + if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, nullptr, &TrainOnTileEnum)) flags |= SF_TRAIN; + tile += TileOffsByDiagDir(exitdir); + break; + } if (enterdir == INVALID_DIAGDIR) { // from 'inside' - train just entered or left the depot if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, nullptr, &TrainOnTileEnum)) flags |= SF_TRAIN; exitdir = GetRailDepotDirection(tile); @@ -496,8 +503,14 @@ static SigSegState UpdateSignalsInBuffer(Owner owner) case MP_RAILWAY: if (IsRailDepot(tile)) { /* 'optimization assert' do not try to update signals in other cases */ - assert(dir == INVALID_DIAGDIR || dir == GetRailDepotDirection(tile)); - _tbdset.Add(tile, INVALID_DIAGDIR); // start from depot inside + if (IsExtendedRailDepot(tile)) { + dir = GetRailDepotDirection(tile); + _tbdset.Add(tile, dir); + _tbdset.Add(tile + TileOffsByDiagDir(dir), ReverseDiagDir(dir)); + } else { + assert(dir == INVALID_DIAGDIR || dir == GetRailDepotDirection(tile)); + _tbdset.Add(tile, INVALID_DIAGDIR); // start from depot inside + } break; } [[fallthrough]]; diff --git a/src/station.cpp b/src/station.cpp index e0dab59067..95475eba6a 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" @@ -268,43 +269,6 @@ void Station::MarkTilesDirty(bool cargo_change) const } } -/* virtual */ uint Station::GetPlatformLength(TileIndex tile) const -{ - assert(this->TileBelongsToRailStation(tile)); - - TileIndexDiff delta = (GetRailStationAxis(tile) == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); - - TileIndex t = tile; - uint len = 0; - do { - t -= delta; - len++; - } while (IsCompatibleTrainStationTile(t, tile)); - - t = tile; - do { - t += delta; - len++; - } while (IsCompatibleTrainStationTile(t, tile)); - - return len - 1; -} - -/* virtual */ uint Station::GetPlatformLength(TileIndex tile, DiagDirection dir) const -{ - TileIndex start_tile = tile; - uint length = 0; - assert(IsRailStationTile(tile)); - assert(dir < DIAGDIR_END); - - do { - length++; - tile += TileOffsByDiagDir(dir); - } while (IsCompatibleTrainStationTile(tile, start_tile)); - - return length; -} - /** * Get the catchment size of an individual station tile. * @param tile Station tile to get catchment size of. @@ -727,6 +691,56 @@ 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() +{ + if (this->hangar == nullptr) return; + + /* TODO Check this. */ + 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(); + } + + this->hangar->Disuse(); + 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..531339b85b 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. @@ -483,9 +488,6 @@ public: void MoveSign(TileIndex new_xy) override; void AfterStationTileSetChange(bool adding, StationType type); - - uint GetPlatformLength(TileIndex tile, DiagDirection dir) const override; - uint GetPlatformLength(TileIndex tile) const override; void RecomputeCatchment(bool no_clear_nearby_lists = false); static void RecomputeCatchmentForAll(); diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 0dd4edb779..9d4da2f958 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -67,6 +67,8 @@ #include "timer/timer_game_tick.h" #include "cheat_type.h" #include "road_func.h" +#include "depot_base.h" +#include "platform_func.h" #include "widgets/station_widget.h" @@ -1239,9 +1241,10 @@ CommandCost FindJoiningWaypoint(StationID existing_waypoint, StationID waypoint_ static void FreeTrainReservation(Train *v) { FreeTrainTrackReservation(v); - if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), false); + if (IsPlatformTile(v->tile)) SetPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), false); v = v->Last(); - if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), false); + if (IsPlatformTile(v->tile)) SetPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), false); + } /** @@ -1250,10 +1253,10 @@ static void FreeTrainReservation(Train *v) */ static void RestoreTrainReservation(Train *v) { - if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), true); + if (IsPlatformTile(v->tile)) SetPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), true); TryPathReserve(v, true, true); v = v->Last(); - if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), true); + if (IsPlatformTile(v->tile)) SetPlatformReservation(v->tile, TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir())), true); } /** @@ -1521,21 +1524,21 @@ CommandCost CmdBuildRailStation(DoCommandFlag flags, TileIndex tile_org, RailTyp TileIndex platform_end = tile; /* We can only account for tiles that are reachable from this tile, so ignore primarily blocked tiles while finding the platform begin and end. */ - for (TileIndex next_tile = platform_begin - tile_offset; IsCompatibleTrainStationTile(next_tile, platform_begin); next_tile -= tile_offset) { + for (TileIndex next_tile = platform_begin - tile_offset; IsCompatiblePlatformTile(next_tile, platform_begin); next_tile -= tile_offset) { platform_begin = next_tile; } - for (TileIndex next_tile = platform_end + tile_offset; IsCompatibleTrainStationTile(next_tile, platform_end); next_tile += tile_offset) { + for (TileIndex next_tile = platform_end + tile_offset; IsCompatiblePlatformTile(next_tile, platform_end); next_tile += tile_offset) { platform_end = next_tile; } /* If there is at least on reservation on the platform, we reserve the whole platform. */ bool reservation = false; for (TileIndex t = platform_begin; !reservation && t <= platform_end; t += tile_offset) { - reservation = HasStationReservation(t); + reservation = HasPlatformReservation(t); } if (reservation) { - SetRailStationPlatformReservation(platform_begin, dir, true); + SetPlatformReservation(platform_begin, dir, true); } } @@ -2539,6 +2542,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 +2637,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 +2681,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 +3695,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/station_map.h b/src/station_map.h index 906ad6193b..0869d49db5 100644 --- a/src/station_map.h +++ b/src/station_map.h @@ -521,28 +521,6 @@ inline TrackBits GetRailStationTrackBits(Tile t) return AxisToTrackBits(GetRailStationAxis(t)); } -/** - * Check if a tile is a valid continuation to a railstation tile. - * The tile \a test_tile is a valid continuation to \a station_tile, if all of the following are true: - * \li \a test_tile is a rail station tile - * \li the railtype of \a test_tile is compatible with the railtype of \a station_tile - * \li the tracks on \a test_tile and \a station_tile are in the same direction - * \li both tiles belong to the same station - * \li \a test_tile is not blocked (@see IsStationTileBlocked) - * @param test_tile Tile to test - * @param station_tile Station tile to compare with - * @pre IsRailStationTile(station_tile) - * @return true if the two tiles are compatible - */ -inline bool IsCompatibleTrainStationTile(Tile test_tile, Tile station_tile) -{ - assert(IsRailStationTile(station_tile)); - return IsRailStationTile(test_tile) && !IsStationTileBlocked(test_tile) && - IsCompatibleRail(GetRailType(test_tile), GetRailType(station_tile)) && - GetRailStationAxis(test_tile) == GetRailStationAxis(station_tile) && - GetStationIndex(test_tile) == GetStationIndex(station_tile); -} - /** * Get the reservation state of the rail station * @pre HasStationRail(t) diff --git a/src/table/settings/game_settings.ini b/src/table/settings/game_settings.ini index 07adda5cc9..1b43b8eff4 100644 --- a/src/table/settings/game_settings.ini +++ b/src/table/settings/game_settings.ini @@ -21,6 +21,9 @@ 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 void DepotSettingsChanged(int32_t new_value); static const SettingVariant _game_settings_table[] = { [post-amble] @@ -145,6 +148,98 @@ str = STR_CONFIG_SETTING_DISTANT_JOIN_STATIONS strhelp = STR_CONFIG_SETTING_DISTANT_JOIN_STATIONS_HELPTEXT post_cb = [](auto) { CloseWindowById(WC_SELECT_STATION, 0); } +[SDT_VAR] +var = depot.rail_depot_types +type = SLE_UINT8 +from = SLV_EXTENDED_DEPOTS +flags = SF_GUI_DROPDOWN +def = 1 +min = 1 +max = 3 +interval = 1 +str = STR_CONFIG_SETTING_RAIL_DEPOT_TYPES +strhelp = STR_CONFIG_SETTING_RAIL_DEPOT_TYPES_HELPTEXT +strval = STR_CONFIG_SETTING_ONLY_STANDARD_DEPOT +post_cb = DepotSettingsChanged +cat = SC_EXPERT + +[SDT_VAR] +var = depot.road_depot_types +type = SLE_UINT8 +from = SLV_EXTENDED_DEPOTS +flags = SF_GUI_DROPDOWN +def = 1 +min = 1 +max = 3 +interval = 1 +str = STR_CONFIG_SETTING_ROAD_DEPOT_TYPES +strhelp = STR_CONFIG_SETTING_ROAD_DEPOT_TYPES_HELPTEXT +strval = STR_CONFIG_SETTING_ONLY_STANDARD_DEPOT +post_cb = DepotSettingsChanged +cat = SC_EXPERT + +[SDT_VAR] +var = depot.water_depot_types +type = SLE_UINT8 +from = SLV_EXTENDED_DEPOTS +flags = SF_GUI_DROPDOWN +def = 1 +min = 1 +max = 3 +interval = 1 +str = STR_CONFIG_SETTING_WATER_DEPOT_TYPES +strhelp = STR_CONFIG_SETTING_WATER_DEPOT_TYPES_HELPTEXT +strval = STR_CONFIG_SETTING_ONLY_STANDARD_DEPOT +post_cb = DepotSettingsChanged +cat = SC_EXPERT + +[SDT_BOOL] +var = depot.adjacent_depots +from = SLV_DEPOT_SPREAD +def = true +cat = SC_EXPERT + +[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 = 1 +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/table/track_land.h b/src/table/track_land.h index 70ea770bfe..d8275743ed 100644 --- a/src/table/track_land.h +++ b/src/table/track_land.h @@ -33,18 +33,18 @@ static const DrawTileSeqStruct _depot_gfx_NW[] = { TILE_SEQ_END() }; -static const DrawTileSprites _depot_gfx_table[] = { +static const DrawTileSprites _depot_gfx_gui_table[] = { { {SPR_FLAT_GRASS_TILE, PAL_NONE}, _depot_gfx_NE }, { {SPR_RAIL_TRACK_Y, PAL_NONE}, _depot_gfx_SE }, { {SPR_RAIL_TRACK_X, PAL_NONE}, _depot_gfx_SW }, { {SPR_FLAT_GRASS_TILE, PAL_NONE}, _depot_gfx_NW } }; -static const DrawTileSprites _depot_invisible_gfx_table[] = { - { {SPR_RAIL_TRACK_X, PAL_NONE}, _depot_gfx_NE }, - { {SPR_RAIL_TRACK_Y, PAL_NONE}, _depot_gfx_SE }, - { {SPR_RAIL_TRACK_X, PAL_NONE}, _depot_gfx_SW }, - { {SPR_RAIL_TRACK_Y, PAL_NONE}, _depot_gfx_NW } +static const DrawTileSprites _depot_gfx_table[] = { + { {SPR_FLAT_GRASS_TILE, PAL_NONE}, _depot_gfx_NE }, + { {SPR_FLAT_GRASS_TILE, PAL_NONE}, _depot_gfx_SE }, + { {SPR_FLAT_GRASS_TILE, PAL_NONE}, _depot_gfx_SW }, + { {SPR_FLAT_GRASS_TILE, PAL_NONE}, _depot_gfx_NW } }; #undef TILE_SEQ_LINE diff --git a/src/tile_cmd.h b/src/tile_cmd.h index 88f36fef86..486dca11e3 100644 --- a/src/tile_cmd.h +++ b/src/tile_cmd.h @@ -19,9 +19,10 @@ /** The returned bits of VehicleEnterTile. */ enum VehicleEnterTileStatus { - VETS_ENTERED_STATION = 1, ///< The vehicle entered a station - VETS_ENTERED_WORMHOLE = 2, ///< The vehicle either entered a bridge, tunnel or depot tile (this includes the last tile of the bridge/tunnel) - VETS_CANNOT_ENTER = 3, ///< The vehicle cannot enter the tile + VETS_ENTERED_STATION = 1, ///< The vehicle entered a station. + VETS_ENTERED_WORMHOLE = 2, ///< The vehicle either entered a bridge, tunnel or depot tile (this includes the last tile of the bridge/tunnel). + VETS_CANNOT_ENTER = 3, ///< The vehicle cannot enter the tile. + VETS_ENTERED_DEPOT_PLATFORM = 4, ///< The vehicle entered a depot platform. /** * Shift the VehicleEnterTileStatus this many bits @@ -32,10 +33,11 @@ enum VehicleEnterTileStatus { VETS_STATION_MASK = 0xFFFF << VETS_STATION_ID_OFFSET, /** Bit sets of the above specified bits */ - VETSB_CONTINUE = 0, ///< The vehicle can continue normally - VETSB_ENTERED_STATION = 1 << VETS_ENTERED_STATION, ///< The vehicle entered a station - VETSB_ENTERED_WORMHOLE = 1 << VETS_ENTERED_WORMHOLE, ///< The vehicle either entered a bridge, tunnel or depot tile (this includes the last tile of the bridge/tunnel) - VETSB_CANNOT_ENTER = 1 << VETS_CANNOT_ENTER, ///< The vehicle cannot enter the tile + VETSB_CONTINUE = 0, ///< The vehicle can continue normally. + VETSB_ENTERED_STATION = 1 << VETS_ENTERED_STATION, ///< The vehicle entered a station. + VETSB_ENTERED_WORMHOLE = 1 << VETS_ENTERED_WORMHOLE, ///< The vehicle either entered a bridge, tunnel or depot tile (this includes the last tile of the bridge/tunnel). + VETSB_CANNOT_ENTER = 1 << VETS_CANNOT_ENTER, ///< The vehicle cannot enter the tile. + VETSB_ENTERED_DEPOT_PLATFORM = 1 << VETS_ENTERED_DEPOT_PLATFORM, ///< The vehicle entered a depot platform. }; DECLARE_ENUM_AS_BIT_SET(VehicleEnterTileStatus) 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/timer/timer_game_tick.h b/src/timer/timer_game_tick.h index 02ae2b16ff..7af0024bd4 100644 --- a/src/timer/timer_game_tick.h +++ b/src/timer/timer_game_tick.h @@ -78,6 +78,7 @@ public: static constexpr TimerGameTick::Ticks STATION_RATING_TICKS = 185; ///< Cycle duration for updating station rating. static constexpr TimerGameTick::Ticks STATION_ACCEPTANCE_TICKS = 250; ///< Cycle duration for updating station acceptance. static constexpr TimerGameTick::Ticks STATION_LINKGRAPH_TICKS = 504; ///< Cycle duration for cleaning dead links. + static constexpr TimerGameTick::Ticks DEPOT_REMOVAL_TICKS = 250; ///< Cycle duration for cleaning demolished depots. static constexpr TimerGameTick::Ticks CARGO_AGING_TICKS = 185; ///< Cycle duration for aging cargo. static constexpr TimerGameTick::Ticks INDUSTRY_PRODUCE_TICKS = 256; ///< Cycle duration for industry production. static constexpr TimerGameTick::Ticks TOWN_GROWTH_TICKS = 70; ///< Cycle duration for towns trying to grow (this originates from the size of the town array in TTD). diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 79e9950791..e50b2044ce 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -44,6 +44,7 @@ #include "core/random_func.hpp" #include "core/backup_type.hpp" #include "depot_base.h" +#include "depot_func.h" #include "object_map.h" #include "object_base.h" #include "ai/ai.hpp" @@ -1254,7 +1255,7 @@ static bool CanRoadContinueIntoNextTile(const Town *t, const TileIndex tile, con /* If the next tile is a road depot, allow if it's facing the right way. */ if (IsTileType(next_tile, MP_ROAD)) { - return IsRoadDepot(next_tile) && GetRoadDepotDirection(next_tile) == ReverseDiagDir(road_dir); + return IsRoadDepot(next_tile) && (GetRoadBits(next_tile, RTT_ROAD) & DiagDirToRoadBits(ReverseDiagDir(road_dir))) != ROAD_NONE; } /* If the next tile is a railroad track, check if towns are allowed to build level crossings. @@ -3005,6 +3006,8 @@ CommandCost CmdRenameTown(DoCommandFlag flags, TownID town_id, const std::string ClearAllStationCachedNames(); ClearAllIndustryCachedNames(); UpdateAllStationVirtCoords(); + UpdateAllDepotVirtCoords(); + RebuildViewportKdtree(); } return CommandCost(); } diff --git a/src/track_type.h b/src/track_type.h index e3c3f22b67..89a30cd5d2 100644 --- a/src/track_type.h +++ b/src/track_type.h @@ -14,7 +14,8 @@ /** * These are used to specify a single track. - * Can be translated to a trackbit with TrackToTrackbit + * Can be translated to a trackbit with TrackToTrackbit. + * TRACK_WORMHOLE and TRACK_DEPOT do not represent single tracks but states; they cannot be translated to trackbits. */ enum Track : uint8_t { TRACK_BEGIN = 0, ///< Used for iterations @@ -24,7 +25,9 @@ enum Track : uint8_t { TRACK_LOWER = 3, ///< Track in the lower corner of the tile (south) TRACK_LEFT = 4, ///< Track in the left corner of the tile (west) TRACK_RIGHT = 5, ///< Track in the right corner of the tile (east) - TRACK_END, ///< Used for iterations + TRACK_END = 6, ///< Used for iterations + TRACK_WORMHOLE = TRACK_END, + TRACK_DEPOT = 7, INVALID_TRACK = 0xFF, ///< Flag for an invalid track }; @@ -49,8 +52,8 @@ enum TrackBits : uint8_t { TRACK_BIT_3WAY_NW = TRACK_BIT_Y | TRACK_BIT_UPPER | TRACK_BIT_LEFT, ///< "Arrow" to the north-west TRACK_BIT_ALL = TRACK_BIT_CROSS | TRACK_BIT_HORZ | TRACK_BIT_VERT, ///< All possible tracks TRACK_BIT_MASK = 0x3FU, ///< Bitmask for the first 6 bits - TRACK_BIT_WORMHOLE = 0x40U, ///< Bitflag for a wormhole (used for tunnels) - TRACK_BIT_DEPOT = 0x80U, ///< Bitflag for a depot + TRACK_BIT_WORMHOLE = 1U << TRACK_WORMHOLE, ///< Bitflag for a wormhole (used for tunnels) + TRACK_BIT_DEPOT = 1U << TRACK_DEPOT, ///< Bitflag for a depot INVALID_TRACK_BIT = 0xFF, ///< Flag for an invalid trackbits value }; DECLARE_ENUM_AS_BIT_SET(TrackBits) diff --git a/src/train.h b/src/train.h index bbf1e04365..9fcd925c82 100644 --- a/src/train.h +++ b/src/train.h @@ -21,6 +21,9 @@ struct Train; +static const uint8_t _vehicle_initial_x_fract[4] = {10, 8, 4, 8}; +static const uint8_t _vehicle_initial_y_fract[4] = { 8, 4, 8, 10}; + /** Rail vehicle flags. */ enum VehicleRailFlags { VRF_REVERSING = 0, @@ -66,7 +69,7 @@ int GetTrainStopLocation(StationID station_id, TileIndex tile, const Train *v, i void GetTrainSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); bool TrainOnCrossing(TileIndex tile); -void NormalizeTrainVehInDepot(const Train *u); +void NormalizeTrainVehInDepot(const Train *u, DoCommandFlag flags = DC_EXEC); /** Variables that are cached to improve performance and such */ struct TrainCache { @@ -120,7 +123,7 @@ struct Train final : public GroundVehicle { Money GetRunningCost() const override; int GetCursorImageOffset() const; int GetDisplayImageWidth(Point *offset = nullptr) const; - bool IsInDepot() const override { return this->track == TRACK_BIT_DEPOT; } + bool IsInDepot() const override { return HasBit((uint8_t)this->track, TRACK_DEPOT); } bool Tick() override; void OnNewCalendarDay() override; void OnNewEconomyDay() override; @@ -353,4 +356,8 @@ protected: // These functions should not be called outside acceleration code. } }; +bool HasCompatibleDepotTile(TileIndex tile, const Train *t); +bool HandleTrainEnterDepot(Train *v); +bool CheckReverseTrain(const Train *v); + #endif /* TRAIN_H */ diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 9664204f14..b1022810bf 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -38,6 +38,10 @@ #include "misc_cmd.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" +#include "depot_base.h" +#include "platform_func.h" +#include "depot_map.h" +#include "train_placement.h" #include "table/strings.h" #include "table/train_sprites.h" @@ -51,9 +55,6 @@ static TileIndex TrainApproachingCrossingTile(const Train *v); static void CheckIfTrainNeedsService(Train *v); static void CheckNextTrainTile(Train *v); -static const uint8_t _vehicle_initial_x_fract[4] = {10, 8, 4, 8}; -static const uint8_t _vehicle_initial_y_fract[4] = { 8, 4, 8, 10}; - template <> bool IsValidImageIndex(uint8_t image_index) { @@ -262,9 +263,9 @@ void Train::ConsistChanged(ConsistChangeFlags allowed_changes) */ int GetTrainStopLocation(StationID station_id, TileIndex tile, const Train *v, int *station_ahead, int *station_length) { - const Station *st = Station::Get(station_id); - *station_ahead = st->GetPlatformLength(tile, DirToDiagDir(v->direction)) * TILE_SIZE; - *station_length = st->GetPlatformLength(tile) * TILE_SIZE; + assert(IsRailStationTile(tile)); + *station_ahead = GetPlatformLength(tile, DirToDiagDir(v->direction)) * TILE_SIZE; + *station_length = GetPlatformLength(tile) * TILE_SIZE; /* Default to the middle of the station for stations stops that are not in * the order list like intermediate stations when non-stop is disabled */ @@ -604,6 +605,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 +675,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 +708,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,12 +736,13 @@ 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 !(w->vehstatus & VS_CRASHED)) { ///< Not crashed/flooded - if (Command::Do(DC_EXEC, v->index, w->Last()->index, true).Succeeded()) { + if (Command::Do(flags | DC_EXEC, v->index, w->Last()->index, true).Succeeded()) { break; } } @@ -689,13 +753,16 @@ static CommandCost CmdBuildRailWagon(DoCommandFlag flags, TileIndex tile, const } /** Move all free vehicles in the depot to the train */ -void NormalizeTrainVehInDepot(const Train *u) +void NormalizeTrainVehInDepot(const Train *u, DoCommandFlag flags) { assert(u->IsEngine()); + assert(flags & DC_EXEC); + + 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 (Command::Do(DC_EXEC, v->index, u->index, true).Failed()) { + if (v->IsFreeWagon() && v->IsInDepot() && + GetDepotIndex(v->tile) == dep_id) { + if (Command::Do(flags, v->index, u->index, true).Failed()) { break; } } @@ -748,13 +815,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); @@ -816,6 +884,10 @@ CommandCost CmdBuildRailVehicle(DoCommandFlag flags, TileIndex tile, const Engin UpdateTrainGroupID(v); CheckConsistencyOfArticulatedVehicle(v); + + TrainPlacement train_placement; + train_placement.LiftTrain(v, flags); + train_placement.PlaceTrain(v, flags); } return CommandCost(); @@ -824,10 +896,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) { @@ -994,6 +1066,10 @@ static CommandCost CheckTrainAttachment(Train *t) /* No multi-part train, no need to check. */ if (t == nullptr || t->Next() == nullptr) return CommandCost(); + TrainPlacement tp; + tp.LookForPlaceInDepot(t, false); + if (tp.info == PI_FAILED_PLATFORM_TYPE) 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 +1305,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 { @@ -1270,6 +1346,13 @@ CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID bool original_src_head_front_engine = original_src_head->IsFrontEngine(); bool original_dst_head_front_engine = original_dst_head != nullptr && original_dst_head->IsFrontEngine(); + TrainPlacement train_placement_src; + TrainPlacement train_placement_dst; + train_placement_src.LiftTrain(src_head, flags); + train_placement_dst.LiftTrain(dst_head, flags); + + assert(src_head != nullptr); + /* (Re)arrange the trains in the wanted arrangement. */ ArrangeTrains(&dst_head, dst, &src_head, src, move_chain); @@ -1282,6 +1365,8 @@ CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID /* Restore the train we had. */ RestoreTrainBackup(original_src); RestoreTrainBackup(original_dst); + train_placement_src.PlaceTrain(original_src_head, flags & ~DC_EXEC); + if (src_head != dst_head) train_placement_dst.PlaceTrain(original_dst_head, flags & ~DC_EXEC); return ret; } } @@ -1362,13 +1447,21 @@ CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID if (src_head != nullptr) src_head->First()->MarkDirty(); if (dst_head != nullptr) dst_head->First()->MarkDirty(); + bool reverse_emplacement_order = !train_placement_src.placed && train_placement_dst.placed; + + if (!reverse_emplacement_order) train_placement_src.PlaceTrain(src_head, flags); + if (src_head != dst_head) train_placement_dst.PlaceTrain(dst_head, flags); + if (reverse_emplacement_order) train_placement_src.PlaceTrain(src_head, flags); + /* 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. */ RestoreTrainBackup(original_src); RestoreTrainBackup(original_dst); + train_placement_src.PlaceTrain(original_src_head, flags); + if (src_head != dst_head) train_placement_dst.PlaceTrain(original_dst_head, flags); } return CommandCost(); @@ -1393,6 +1486,9 @@ CommandCost CmdSellRailWagon(DoCommandFlag flags, Vehicle *t, bool sell_chain, b if (v->IsRearDualheaded()) return_cmd_error(STR_ERROR_REAR_ENGINE_FOLLOW_FRONT); + TrainPlacement train_placement; + train_placement.LiftTrain(first, flags); + /* First make a backup of the order of the train. That way we can do * whatever we want with the order and later on easily revert. */ TrainList original; @@ -1410,12 +1506,14 @@ CommandCost CmdSellRailWagon(DoCommandFlag flags, Vehicle *t, bool sell_chain, b if (ret.Failed()) { /* Restore the train we had. */ RestoreTrainBackup(original); + train_placement.PlaceTrain(first, flags); return ret; } if (first->orders == nullptr && !OrderList::CanAllocateItem()) { /* Restore the train we had. */ RestoreTrainBackup(original); + train_placement.PlaceTrain(first, flags); return_cmd_error(STR_ERROR_NO_MORE_SPACE_FOR_ORDERS); } @@ -1445,16 +1543,18 @@ CommandCost CmdSellRailWagon(DoCommandFlag flags, Vehicle *t, bool sell_chain, b /* We need to update the information about the train. */ NormaliseTrainHead(new_head); + train_placement.PlaceTrain(new_head, flags); /* 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' */ delete sell_head; } else { - /* We don't want to execute what we're just tried. */ + /* We don't want to execute what we have just tried. */ RestoreTrainBackup(original); + train_placement.PlaceTrain(first, flags); } return cost; @@ -1965,8 +2065,12 @@ static bool IsWholeTrainInsideDepot(const Train *v) void ReverseTrainDirection(Train *v) { if (IsRailDepotTile(v->tile)) { - if (IsWholeTrainInsideDepot(v)) return; - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + if (IsExtendedDepot(v->tile)) { + if ((v->track & TRACK_BIT_DEPOT) != 0 && (v->track & ~TRACK_BIT_DEPOT) != 0) return; + } else { + if (IsWholeTrainInsideDepot(v)) return; + } + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); } /* Clear path reservation in front if train is not stuck. */ @@ -1989,7 +2093,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); @@ -2030,9 +2134,9 @@ void ReverseTrainDirection(Train *v) !IsPbsSignal(GetSignalType(v->tile, FindFirstTrack(v->track)))); /* If we are on a depot tile facing outwards, do not treat the current tile as safe. */ - if (IsRailDepotTile(v->tile) && TrackdirToExitdir(v->GetVehicleTrackdir()) == GetRailDepotDirection(v->tile)) first_tile_okay = false; + if (IsStandardRailDepotTile(v->tile) && TrackdirToExitdir(v->GetVehicleTrackdir()) == GetRailDepotDirection(v->tile)) first_tile_okay = false; - if (IsRailStationTile(v->tile)) SetRailStationPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), true); + if (IsPlatformTile(v->tile)) SetPlatformReservation(v->tile, TrackdirToExitdir(v->GetVehicleTrackdir()), true); if (TryPathReserve(v, false, first_tile_okay)) { /* Do a look-ahead now in case our current tile was already a safe tile. */ CheckNextTrainTile(v); @@ -2079,7 +2183,14 @@ 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)) { + if (IsExtendedDepot(front->tile)) { + TrainPlacement tp; + tp.LiftTrain(front, flags); + tp.PlaceTrain(front, flags & ~DC_EXEC); + } + SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(front->tile)); + } SetWindowDirty(WC_VEHICLE_DETAILS, front->index); SetWindowDirty(WC_VEHICLE_VIEW, front->index); SetWindowClassesDirty(WC_TRAINS_LIST); @@ -2089,6 +2200,9 @@ CommandCost CmdReverseTrainDirection(DoCommandFlag flags, VehicleID veh_id, bool if (!v->IsPrimaryVehicle()) return CMD_ERROR; if ((v->vehstatus & VS_CRASHED) || v->breakdown_ctr != 0) return CMD_ERROR; + /* Do not reverse while servicing. */ + if (IsExtendedRailDepotTile(v->tile) && (v->track & TRACK_BIT_DEPOT) != 0 && (v->track & ~TRACK_BIT_DEPOT) != 0) return CMD_ERROR; + if (flags & DC_EXEC) { /* Properly leave the station if we are loading and won't be loading anymore */ if (v->current_order.IsType(OT_LOADING)) { @@ -2212,7 +2326,7 @@ static void CheckNextTrainTile(Train *v) switch (v->current_order.GetType()) { /* Exit if we reached our destination depot. */ case OT_GOTO_DEPOT: - if (v->tile == v->dest_tile) return; + if (IsRailDepotTile(v->tile) && v->current_order.ShouldStopAtDepot(GetDepotIndex(v->tile))) return; break; case OT_GOTO_WAYPOINT: @@ -2258,6 +2372,53 @@ static void CheckNextTrainTile(Train *v) } } +bool HandleTrainEnterDepot(Train *v) +{ + assert(IsRailDepotTile(v->tile)); + + if (IsExtendedRailDepot(v->tile)) { + v->cur_speed = 0; + Train *t = Train::From(v); + for (Train *u = t; u != nullptr; u = u->Next()) { + if (!IsCompatibleTrainDepotTile(u->tile, t->tile)) { + SetDParam(0, v->index); + AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_TOO_LONG_FOR_SERVICING, v->index); + return false; + } + } + + for (Train *u = t; u != nullptr; u = u->Next()) u->track |= TRACK_BIT_DEPOT; + t->force_proceed = TFP_NONE; + ClrBit(t->flags, VRF_TOGGLE_REVERSE); + UpdateExtendedDepotReservation(t, true); + v->UpdateViewport(true, true); + SetWindowClassesDirty(WC_TRAINS_LIST); + SetWindowDirty(WC_VEHICLE_VIEW, v->index); + + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); + v->StartService(); + } else { + /* Clear path reservation */ + SetDepotReservation(v->tile, false); + VehicleEnterDepot(v); + } + + return true; +} + +bool CheckReverseTrain(const Train *v) +{ + if (_settings_game.difficulty.line_reverse_mode != 0 || + v->track == TRACK_BIT_DEPOT || v->track == TRACK_BIT_WORMHOLE || + !(v->direction & 1)) { + return false; + } + + assert(v->track != TRACK_BIT_NONE); + + return YapfTrainCheckReverse(v); +} + /** * Will the train stay in the depot the next tick? * @param v %Train to check. @@ -2266,15 +2427,80 @@ static void CheckNextTrainTile(Train *v) static bool CheckTrainStayInDepot(Train *v) { /* bail out if not all wagons are in the same depot or not in a depot at all */ - for (const Train *u = v; u != nullptr; u = u->Next()) { - if (u->track != TRACK_BIT_DEPOT || u->tile != v->tile) return false; - } + if (!v->IsInDepot()) return false; + assert(IsRailDepotTile(v->tile)); - /* 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); - return true; + DepotID depot_id = GetDepotIndex(v->tile); + if (IsExtendedRailDepot(v->tile)) { + /* If not placed, try it. If not possible, exit. */ + if (CheckIfTrainNeedsPlacement(v)) { + /* If stuck, wait a little bit, so we can avoid + * trying placing it as much as we can. */ + bool already_stuck = false; + bool send_message = false; + if (HasBit(v->flags, VRF_TRAIN_STUCK)) { + already_stuck = true; + v->wait_counter++; + if (v->wait_counter % (1 << 7) != 0) { + return true; + } else if (v->wait_counter % (1 << 11) == 0) { + send_message = true; + } + ClrBit(v->flags, VRF_TRAIN_STUCK); + } + + TrainPlacement train_placement; + train_placement.LiftTrain(v, DC_EXEC); + train_placement.LookForPlaceInDepot(v, true); + if (train_placement.info < PI_FAILED_END) { + if (send_message) { + ClrBit(v->flags, VRF_TRAIN_STUCK); + /* Show message to player. */ + if (_settings_client.gui.lost_vehicle_warn && v->owner == _local_company) { + SetDParam(0, v->index); + AddVehicleAdviceNewsItem(STR_ADVICE_PLATFORM_TYPE + train_placement.info - PI_ERROR_BEGIN, v->index); + } + } + if (already_stuck) { + SetBit(v->flags, VRF_TRAIN_STUCK); + } else { + MarkTrainAsStuck(v); + } + return true; + } else { + VehicleServiceInExtendedDepot(v); + train_placement.PlaceTrain(v, DC_EXEC); + if (!IsExtendedDepot(v->tile)) return true; + } + } else { + VehicleServiceInExtendedDepot(v); + } + + for (Train *u = v; u != nullptr; u = u->Next()) u->track &= ~TRACK_BIT_DEPOT; + + v->cur_speed = 0; + v->UpdateAcceleration(); + ProcessOrders(v); + if (CheckReverseTrain(v)) ReverseTrainDirection(v); + UpdateExtendedDepotReservation(v, false); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); + + /* Check whether it is safe to exit the depot. */ + if (UpdateSignalsOnSegment(v->tile, VehicleExitDir(v->direction, v->track), v->owner) == SIGSEG_PBS || _settings_game.pf.reserve_paths) { + if (!TryPathReserve(v, true, true)) return true; + } + return false; + } else { + for (const Train *u = v; u != nullptr; u = u->Next()) { + if (!u->IsInDepot() || u->tile != v->tile) return false; + } + + /* 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, depot_id); + return true; + } } /* Check if we should wait here for unbunching. */ @@ -2302,9 +2528,11 @@ 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); + if (!HasDepotReservation(v->tile)) HandleTrainEnterDepot(v); return true; } @@ -2334,7 +2562,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, depot_id); return false; } @@ -2369,12 +2597,12 @@ static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_ } } } - } else if (IsRailStationTile(tile)) { + } else if (IsRailStationTile(tile) || IsExtendedRailDepotTile(tile)) { TileIndex new_tile = TileAddByDiagDir(tile, dir); /* If the new tile is not a further tile of the same station, we * clear the reservation for the whole platform. */ - if (!IsCompatibleTrainStationTile(new_tile, tile)) { - SetRailStationPlatformReservation(tile, ReverseDiagDir(dir), false); + if (!IsCompatiblePlatformTile(new_tile, tile)) { + SetPlatformReservation(tile, ReverseDiagDir(dir), false); } } else { /* Any other tile */ @@ -2392,11 +2620,12 @@ void FreeTrainTrackReservation(const Train *v) TileIndex tile = v->tile; Trackdir td = v->GetVehicleTrackdir(); - bool free_tile = !(IsRailStationTile(v->tile) || IsTileType(v->tile, MP_TUNNELBRIDGE)); + bool free_tile = !(IsRailStationTile(v->tile) || IsExtendedRailDepotTile(v->tile) || IsTileType(v->tile, MP_TUNNELBRIDGE)); StationID station_id = IsRailStationTile(v->tile) ? GetStationIndex(v->tile) : INVALID_STATION; + DepotID depot_id = IsExtendedRailDepotTile(v->tile) ? GetDepotIndex(v->tile) : INVALID_DEPOT; /* Can't be holding a reservation if we enter a depot. */ - if (IsRailDepotTile(tile) && TrackdirToExitdir(td) != GetRailDepotDirection(tile)) return; + if (IsStandardRailDepotTile(tile) && TrackdirToExitdir(td) != GetRailDepotDirection(tile)) return; if (v->track == TRACK_BIT_DEPOT) { /* Front engine is in a depot. We enter if some part is not in the depot. */ for (const Train *u = v; u != nullptr; u = u->Next()) { @@ -2436,7 +2665,7 @@ void FreeTrainTrackReservation(const Train *v) } /* Don't free first station/bridge/tunnel if we are on it. */ - if (free_tile || (!(ft.m_is_station && GetStationIndex(ft.m_new_tile) == station_id) && !ft.m_is_tunnel && !ft.m_is_bridge)) ClearPathReservation(v, tile, td); + if (free_tile || (!(ft.m_is_station && GetStationIndex(ft.m_new_tile) == station_id) && !(ft.m_is_extended_depot && GetDepotIndex(ft.m_new_tile) == depot_id) && !ft.m_is_tunnel && !ft.m_is_bridge)) ClearPathReservation(v, tile, td); free_tile = true; } @@ -2495,7 +2724,7 @@ static PBSTileInfo ExtendTrainReservation(const Train *v, TrackBits *new_tracks, } /* Station, depot or waypoint are a possible target. */ - bool target_seen = ft.m_is_station || (IsTileType(ft.m_new_tile, MP_RAILWAY) && !IsPlainRail(ft.m_new_tile)); + bool target_seen = ft.m_is_station || ft.m_is_extended_depot || (IsTileType(ft.m_new_tile, MP_RAILWAY) && !IsPlainRail(ft.m_new_tile)); if (target_seen || KillFirstBit(ft.m_new_td_bits) != TRACKDIR_BIT_NONE) { /* Choice found or possible target encountered. * On finding a possible target, we need to stop and let the pathfinder handle the @@ -2852,6 +3081,7 @@ bool TryPathReserve(Train *v, bool mark_as_stuck, bool first_tile_okay) if (mark_as_stuck) MarkTrainAsStuck(v); return false; } else { + assert(IsStandardRailDepotTile(v->tile)); /* Depot not reserved, but the next tile might be. */ TileIndex next_tile = TileAddByDiagDir(v->tile, GetRailDepotDirection(v->tile)); if (HasReservedTracks(next_tile, DiagdirReachesTracks(GetRailDepotDirection(v->tile)))) return false; @@ -2907,19 +3137,6 @@ bool TryPathReserve(Train *v, bool mark_as_stuck, bool first_tile_okay) } -static bool CheckReverseTrain(const Train *v) -{ - if (_settings_game.difficulty.line_reverse_mode != 0 || - v->track == TRACK_BIT_DEPOT || v->track == TRACK_BIT_WORMHOLE || - !(v->direction & 1)) { - return false; - } - - assert(v->track != TRACK_BIT_NONE); - - return YapfTrainCheckReverse(v); -} - /** * Get the location of the next station to visit. * @param station Next station to visit. @@ -3065,6 +3282,7 @@ static bool TrainMovedChangeSignals(TileIndex tile, DiagDirection dir) void Train::ReserveTrackUnderConsist() const { for (const Train *u = this; u != nullptr; u = u->Next()) { + if (u->vehstatus & VS_HIDDEN) continue; switch (u->track) { case TRACK_BIT_WORMHOLE: TryReserveRailTrack(u->tile, DiagDirToDiagTrack(GetTunnelBridgeDirection(u->tile))); @@ -3093,6 +3311,24 @@ uint Train::Crash(bool flooded) /* Remove the reserved path in front of the train if it is not stuck. * Also clear all reserved tracks the train is currently on. */ if (!HasBit(this->flags, VRF_TRAIN_STUCK)) FreeTrainTrackReservation(this); + + if (IsExtendedRailDepotTile(this->tile)) { + if (this->track & ~TRACK_BIT_DEPOT) { + for (Train *v = this; v != nullptr; v = v->Next()) { + v->track &= ~TRACK_BIT_DEPOT; + } + UpdateExtendedDepotReservation(this, false); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(this->tile)); + } + /* Remove reserved tracks of platform ahead. */ + TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(this->GetVehicleTrackdir())); + for (TileIndex pt_tile = this->tile + diff; IsCompatiblePlatformTile(pt_tile, this->tile) && HasDepotReservation(pt_tile); pt_tile += diff) { + if (EnsureNoVisibleVehicleOnGround(pt_tile).Failed()) break; + SetDepotReservation(pt_tile, false); + MarkTileDirtyByTile(pt_tile); + } + } + for (const Train *v = this; v != nullptr; v = v->Next()) { ClearPathReservation(v, v->tile, v->GetVehicleTrackdir()); if (IsTileType(v->tile, MP_TUNNELBRIDGE)) { @@ -3100,6 +3336,23 @@ uint Train::Crash(bool flooded) * if the train has just entered the wormhole. */ SetTunnelBridgeReservation(GetOtherTunnelBridgeEnd(v->tile), false); } + + if (v->Next() == nullptr && (IsRailStationTile(v->tile) || IsExtendedRailDepotTile(v->tile))) { + /* Remove reserved tracks of platform tiles behind the train. */ + TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(v->GetVehicleTrackdir()))); + for (TileIndex pt_tile = v->tile + diff; IsCompatiblePlatformTile(pt_tile, v->tile); pt_tile += diff) { + if (IsExtendedRailDepotTile(pt_tile)) { + if (!HasDepotReservation(pt_tile)) break; + if (EnsureNoVisibleVehicleOnGround(pt_tile).Failed()) break; + SetDepotReservation(pt_tile, false); + } else { + if (!HasStationReservation(pt_tile)) break; + if (EnsureNoVisibleVehicleOnGround(pt_tile).Failed()) break; + SetRailStationReservation(pt_tile, false); + } + MarkTileDirtyByTile(pt_tile); + } + } } /* we may need to update crossing we were approaching, @@ -3112,6 +3365,7 @@ uint Train::Crash(bool flooded) } pass += this->GroundVehicleBase::Crash(flooded); + this->ReserveTrackUnderConsist(); this->crash_anim_pos = flooded ? 4000 : 1; // max 4440, disappear pretty fast when flooded return pass; @@ -3134,10 +3388,6 @@ static uint TrainCrashed(Train *v) Game::NewEvent(new ScriptEventVehicleCrashed(v->index, v->tile, ScriptEventVehicleCrashed::CRASH_TRAIN)); } - /* Try to re-reserve track under already crashed train too. - * Crash() clears the reservation! */ - v->ReserveTrackUnderConsist(); - return num; } @@ -3190,6 +3440,9 @@ static Vehicle *FindTrainCollideEnum(Vehicle *v, void *data) tcc->num += TrainCrashed(tcc->v); tcc->num += TrainCrashed(coll); + /* The crashing of the coll train frees reservation of train v: Reserve again for train v. */ + tcc->v->ReserveTrackUnderConsist(); + return nullptr; // continue searching } @@ -3285,6 +3538,12 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) if (HasBit(r, VETS_ENTERED_STATION)) { /* The new position is the end of the platform */ TrainEnterStation(v, r >> VETS_STATION_ID_OFFSET); + } else if (HasBit(r, VETS_ENTERED_DEPOT_PLATFORM)) { + if (HandleTrainEnterDepot(first)) { + v->UpdatePosition(); + v->UpdateViewport(true, true); + return false; + } } } } else { @@ -3628,7 +3887,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 } @@ -3670,7 +3929,7 @@ static void DeleteLastWagon(Train *v) } /* Update signals */ - if (IsTileType(tile, MP_TUNNELBRIDGE) || IsRailDepotTile(tile)) { + if (IsTileType(tile, MP_TUNNELBRIDGE) || IsStandardRailDepotTile(tile)) { UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, owner); } else { SetSignalsOnBothDir(tile, track, owner); @@ -3821,8 +4080,13 @@ static bool TrainCanLeaveTile(const Train *v) /* entering a depot? */ if (IsRailDepotTile(tile)) { - DiagDirection dir = ReverseDiagDir(GetRailDepotDirection(tile)); - if (DiagDirToDir(dir) == v->direction) return false; + if (IsExtendedRailDepot(tile)) { + Direction dir = DiagDirToDir(GetRailDepotDirection(tile)); + if (dir == v->direction || ReverseDir(dir) == v->direction) return false; + } else { + DiagDirection dir = ReverseDiagDir(GetRailDepotDirection(tile)); + if (DiagDirToDir(dir) == v->direction) return false; + } } return true; @@ -3936,6 +4200,8 @@ static bool TrainLocoHandler(Train *v, bool mode) /* exit if train is stopped */ if ((v->vehstatus & VS_STOPPED) && v->cur_speed == 0) return true; + if (v->ContinueServicing()) return true; + bool valid_order = !v->current_order.IsType(OT_NOTHING) && v->current_order.GetType() != OT_CONDITIONAL; if (ProcessOrders(v) && CheckReverseTrain(v)) { v->wait_counter = 0; @@ -4198,9 +4464,13 @@ Trackdir Train::GetVehicleTrackdir() const { if (this->vehstatus & VS_CRASHED) return INVALID_TRACKDIR; - if (this->track == TRACK_BIT_DEPOT) { + if (this->IsInDepot()) { /* We'll assume the train is facing outwards */ - return DiagDirToDiagTrackdir(GetRailDepotDirection(this->tile)); // Train in depot + if (this->track == TRACK_BIT_DEPOT) + return DiagDirToDiagTrackdir(DirToDiagDir(this->direction)); // Train in depot + Track track = FindFirstTrack(this->track & ~TRACK_BIT_DEPOT); + assert(IsValidTrack(track)); + return TrackDirectionToTrackdir(track, this->direction); } if (this->track == TRACK_BIT_WORMHOLE) { 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/train_placement.cpp b/src/train_placement.cpp new file mode 100644 index 0000000000..0f05da5ebc --- /dev/null +++ b/src/train_placement.cpp @@ -0,0 +1,314 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file train_placement.cpp Handling of trains in depot platforms. */ + +#include "stdafx.h" +#include "error.h" +#include "news_func.h" +#include "company_func.h" +#include "strings_func.h" +#include "platform_func.h" +#include "depot_base.h" +#include "depot_map.h" +#include "train_placement.h" +#include "train.h" + +#include "table/strings.h" + +#include "safeguards.h" + + +/** + * Check if a train can be placed in a given tile. + * @param train The train. + * @param check_tile The tile where we want to check whether it is possible to place the train. + * @param executing False if testing and true if the call is being executed. + * @return whether it found a platform to place the train. + */ +bool TrainPlacement::CheckPlacement(const Train *train, TileIndex check_tile, bool executing) +{ + assert(train != nullptr); + assert(IsRailDepotTile(check_tile)); + + RailType rt = GetRailType(check_tile); + PlacementInfo error_info = PI_FAILED_FREE_WAGGON; + bool is_extended_depot = IsExtendedRailDepot(check_tile); + bool succeeded = !train->IsFreeWagon(); + + if (succeeded) { + error_info = PI_FAILED_PLATFORM_TYPE; + for (const Train *t = train; t != nullptr && succeeded; t = t->Next()) { + RailType rail_type = Engine::Get(t->engine_type)->u.rail.railtype; + if (!IsCompatibleRail(rail_type, rt)) succeeded = false; + } + } + + if (succeeded && is_extended_depot) { + error_info = PI_FAILED_LENGTH; + if (train->gcache.cached_total_length > GetPlatformLength(check_tile) * TILE_SIZE) succeeded = false; + } + + if (succeeded) { + error_info = PI_FAILED_POWER; + bool has_power = false; + for (const Train *t = train; t != nullptr && !has_power; t = t->Next()) { + if (HasPowerOnRail(train->railtype, rt)) has_power = true; + } + if (!has_power) succeeded = false; + } + + if (succeeded && is_extended_depot) { + error_info = PI_FAILED_RESERVED; + + /* Check whether any tile of the platform is reserved. Don't assume all platform + * is reserved as a whole: sections of the platform may be reserved by crashed trains. */ + for (TileIndex tile : GetPlatformTileArea(check_tile)) { + if (HasDepotReservation(tile)) { + succeeded = false; + break; + } + } + } + + if (succeeded && executing) { + /* Do not check for signals if really not executing and action. */ + error_info = PI_FAILED_SIGNALS; + SigSegState seg_state = UpdateSignalsOnSegment(check_tile, INVALID_DIAGDIR, train->owner); + if (train->force_proceed == TFP_NONE && seg_state == SIGSEG_FULL) succeeded = false; + } + + if (succeeded) error_info = PI_SUCCESS; + + if (error_info > this->info) { + this->best_tile = check_tile; + this->info = error_info; + + /* A direction for the train must be choosen: the one that allows the longest train in platform. */ + DiagDirection dir = GetRailDepotDirection(check_tile); + if (is_extended_depot && GetPlatformLength(check_tile, dir) > GetPlatformLength(check_tile, ReverseDiagDir(dir))) { + dir = ReverseDiagDir(dir); + } + this->best_dir = DiagDirToDir(dir); + } + + return succeeded; +} + +/** + * Before placing a train in the rails of a depot, a valid platform must + * be found. This function finds a tile for placing the train (and also gets the direction and track). + * If there is no valid tile, it will be returned as best_tile == INVALID_TILE or info == PI_FAILED_PLATFORM_TYPE. + * @param t The train we want to place in rails. + * @param executing False if testing and true if the call is being executed. + * @pre The train must be inside the rail depot as if it where in a standard depot. + * (i.e. the track is TRACK_BIT_DEPOT, vehicles are hidden...). + */ +void TrainPlacement::LookForPlaceInDepot(const Train *train, bool executing) +{ + assert(train != nullptr); + assert(IsRailDepotTile(train->tile)); + + /* Initialitzation. */ + bool is_extended_depot = IsExtendedRailDepot(train->tile); + this->best_tile = (this->placed || !is_extended_depot) ? train->tile : GetPlatformExtremeTile(train->tile, DirToDiagDir(train->direction)); + assert(IsStandardRailDepot(this->best_tile) || IsAnyStartPlatformTile(this->best_tile)); + this->best_dir = train->direction; + this->info = PI_BEGIN; + + /* First candidate is the original position of the train. */ + if (CheckPlacement(train, this->best_tile, executing)) return; + + /* Check all platforms. */ + Depot *depot = Depot::GetByTile(train->tile); + for (auto &depot_tile : depot->depot_tiles) { + if (CheckPlacement(train, depot_tile, executing)) return; + } +} + +/** + * Check if a train can leave now or when other trains + * move away. It returns whether there is a platform long + * enough and with the appropriate rail type. + * @param train The train. + * @param executing False if testing and true if the call is being executed. + * @return true iff there is a compatible platform long enough. + */ +bool TrainPlacement::CanFindAppropriatePlatform(const Train *train, bool executing) +{ + this->LookForPlaceInDepot(train, executing); + return this->info >= PI_WONT_LEAVE; +} + + +/** + * Lift a train in a depot: keep the positions of the elements of the chain if needed, + * and keep also the original tile, direction and track. + * @param train The train we want to lift. + * @pre The train must be inside a rail depot. + * (i.e. the track is 'valid track | TRACK_BIT_DEPOT' or just 'TRACK_BIT_DEPOT'). + */ +void TrainPlacement::LiftTrain(Train *train, DoCommandFlag flags) +{ + assert(train == nullptr || train->IsInDepot()); + assert(train == nullptr || IsRailDepotTile(train->tile)); + assert(this->placed == false); + + /* Lift the train only if we have a train in an extended depot. */ + if (train == nullptr || !IsExtendedRailDepot(train->tile)) return; + + /* Do not lift in recursive commands of autoreplace. */ + if (flags & DC_AUTOREPLACE) return; + + /* If train is not placed... return, because train is already lifted. */ + if ((train->track & ~TRACK_BIT_DEPOT) == 0) return; + + /* Train is placed in rails: lift it. */ + this->placed = true; + if (flags & DC_EXEC) FreeTrainTrackReservation(train); + + for (Train *t = train; t != nullptr; t = t->Next()) { + // Lift. + t->track = TRACK_BIT_DEPOT; + t->tile = train->tile; + t->x_pos = train->x_pos; + t->y_pos = train->y_pos; + t->UpdatePosition(); + t->UpdateViewport(true, true); + + } + + if ((flags & DC_EXEC) == 0) return; + + SetPlatformReservation(train->tile, false); + UpdateExtendedDepotReservation(train, false); + + UpdateSignalsOnSegment(train->tile, INVALID_DIAGDIR, train->owner); +} + +/** + * When a train is lifted inside a depot, before starting its way again, + * must be placed in rails if in an extended rail depot; this function does all necessary things to do so. + * In general, it's the opposite of #LiftTrain + * @param train The train we want to place in rails. + * @param flags Associated command flags + * @pre The train must be inside the extended rail depot as if in a standard depot. + * (i.e. the track is TRACK_BIT_DEPOT, vehicles are hidden...). + */ +void TrainPlacement::PlaceTrain(Train *train, DoCommandFlag flags) +{ + if (train == nullptr) return; + if (train != train->First()) return; + if (!IsRailDepotTile(train->tile)) return; + if (flags & DC_AUTOREPLACE) return; + + bool executing = (flags & DC_EXEC) != 0; + + /* Look for an appropriate platform. */ + this->LookForPlaceInDepot(train, executing); + assert(!IsExtendedRailDepot(this->best_tile) || IsAnyStartPlatformTile(this->best_tile)); + + if (this->info < PI_FAILED_END || !executing) { + if (!executing) { + /* Restore the train. */ + this->best_tile = train->tile; + this->best_dir = train->direction; + this->info = PI_SUCCESS; + } + + if (!this->placed || (this->info < PI_FAILED_END && executing)) { + for (Train *t = train; t != nullptr; t = t->Next()) { + t->tile = this->best_tile; + t->vehstatus |= VS_HIDDEN; + t->track = TRACK_BIT_DEPOT; + } + if (!executing) return; + train->PowerChanged(); + } + + if (this->info < PI_FAILED_END && executing) { + /* Train cannot leave until changing the depot. Stop the train and send a message. */ + if (info < PI_WONT_LEAVE) { + train->vehstatus |= VS_STOPPED; + /* If vehicle is not stopped and user is the local company, send a message if needed. */ + if ((train->vehstatus & VS_STOPPED) == 0 && train->owner == _local_company && train->IsFrontEngine()) { + SetDParam(0, train->index); + AddVehicleAdviceNewsItem(STR_ADVICE_PLATFORM_TYPE + info - PI_ERROR_BEGIN, train->index); + } + } + return; + } + } + + assert(this->best_tile != INVALID_TILE); + assert(this->best_dir != INVALID_DIR); + assert(IsRailDepotTile(this->best_tile)); + + if (executing) { + train->tile = this->best_tile; + train->track = TrackToTrackBits(GetRailDepotTrack(this->best_tile)); + train->direction = this->best_dir; + train->PowerChanged(); + } + + if (IsStandardRailDepot(this->best_tile)) { + int x = TileX(this->best_tile) * TILE_SIZE + _vehicle_initial_x_fract[DirToDiagDir(this->best_dir)]; + int y = TileY(this->best_tile) * TILE_SIZE + _vehicle_initial_y_fract[DirToDiagDir(this->best_dir)]; + for (Train *t = train; t != nullptr; t = t->Next()) { + t->tile = this->best_tile; + t->direction = this->best_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(); + t->UpdateViewport(true, true); + } + return; + } + + DiagDirection placing_dir = ReverseDiagDir(DirToDiagDir(this->best_dir)); + + static const uint8_t _plat_initial_x_fract[4] = {15, 8, 0, 8}; + static const uint8_t _plat_initial_y_fract[4] = { 8, 0, 8, 15}; + + int x = TileX(this->best_tile) * TILE_SIZE | _plat_initial_x_fract[placing_dir]; + int y = TileY(this->best_tile) * TILE_SIZE | _plat_initial_y_fract[placing_dir]; + + /* Add the offset for the first vehicle. */ + x += TileIndexDiffCByDiagDir(placing_dir).x * (train->gcache.cached_veh_length + 1) / 2; + y += TileIndexDiffCByDiagDir(placing_dir).y * (train->gcache.cached_veh_length + 1) / 2; + + /* Proceed placing the train in the given tile. + * At this point, the first vehicle contains the direction, tile and track. + * We must update positions of all the chain. */ + for (Train *t = train; t != nullptr; t = t->Next()) { + t->vehstatus &= ~VS_HIDDEN; + t->direction = this->best_dir; + t->track = DiagDirToDiagTrackBits(placing_dir) | TRACK_BIT_DEPOT; + t->x_pos = x; + t->y_pos = y; + t->z_pos = GetSlopePixelZ(t->x_pos, t->y_pos); + t->tile = TileVirtXY(t->x_pos,t->y_pos); + + assert(t->z_pos == train->z_pos); + assert(IsExtendedRailDepotTile(t->tile)); + + t->UpdatePosition(); + t->UpdateViewport(true, true); + + int advance = t->CalcNextVehicleOffset(); + x += TileIndexDiffCByDiagDir(placing_dir).x * advance; + y += TileIndexDiffCByDiagDir(placing_dir).y * advance; + } + + SetPlatformReservation(train->tile, true); + UpdateExtendedDepotReservation(train, true); + + UpdateSignalsOnSegment(train->tile, INVALID_DIAGDIR, train->owner); +} diff --git a/src/train_placement.h b/src/train_placement.h new file mode 100644 index 0000000000..dfa1145894 --- /dev/null +++ b/src/train_placement.h @@ -0,0 +1,58 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file train_placement.h Handling of trains in depot platforms. */ + +#ifndef TRAIN_PLA_H +#define TRAIN_PLA_H + +#include "core/enum_type.hpp" +#include "train.h" + + +/* Flags of failure and success when placing a train. */ +enum PlacementInfo { + PI_BEGIN = 0, + PI_FAILED_FREE_WAGGON = PI_BEGIN, // Free waggon: not to be placed. + PI_ERROR_BEGIN, + PI_FAILED_PLATFORM_TYPE = PI_ERROR_BEGIN, // No compatible platforms with train type. + PI_FAILED_LENGTH, // There are compatible platforms but not long enough. + PI_FAILED_POWER, // No engine gets power in the platform. + PI_WONT_LEAVE, + PI_FAILED_RESERVED = PI_WONT_LEAVE, // There are compatible platforms but reserved right now. + PI_FAILED_SIGNALS, // There are compatible platforms not reserved, but signals don't allow placing it now. + PI_FAILED_END, + PI_SUCCESS = PI_FAILED_END, // There is an appropriate platform. + PI_END, +}; + +/* Store position of a train and lift it when necessary. */ +struct TrainPlacement { + bool placed; // True if train is placed in rails. + TileIndex best_tile; // Best tile for the train. + Direction best_dir; // Best direction for the train. + PlacementInfo info; // Info of possible problems in best platform. + + TrainPlacement() : placed(false), + best_tile(INVALID_TILE), + best_dir(INVALID_DIR), + info(PI_FAILED_PLATFORM_TYPE) {} + + bool CheckPlacement(const Train *train, TileIndex tile, bool executing); + void LookForPlaceInDepot(const Train *train, bool executing); + bool CanFindAppropriatePlatform(const Train *train, bool executing); + + void LiftTrain(Train *train, DoCommandFlag flags); + void PlaceTrain(Train *train, DoCommandFlag flags); +}; + +static inline bool CheckIfTrainNeedsPlacement(const Train *train) +{ + return IsExtendedRailDepot(train->tile) && (train->track & ~TRACK_BIT_DEPOT) == 0; +} + +#endif /* TRAIN_PLA_H */ diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 9db7da2fa1..76dc88afd0 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -181,6 +181,117 @@ void VehicleServiceInDepot(Vehicle *v) } while (v != nullptr && v->HasEngineType()); } +/** + * List of vehicles that should check for autoreplace this tick. + * Mapping of vehicle -> leave depot immediately after autoreplace. + */ +using AutoreplaceMap = std::map; +static AutoreplaceMap _vehicles_to_autoreplace; + +void VehicleServiceInExtendedDepot(Vehicle *v) +{ + /* Always work with the front of the vehicle */ + assert(v == v->First()); + assert(IsExtendedDepotTile(v->tile)); + + switch (v->type) { + case VEH_TRAIN: { + SetWindowClassesDirty(WC_TRAINS_LIST); + Train *t = Train::From(v); + t->ConsistChanged(CCF_ARRANGE); + t->UpdateViewport(true, true); + break; + } + + case VEH_SHIP: { + SetWindowClassesDirty(WC_SHIPS_LIST); + Ship *ship = Ship::From(v); + ship->UpdateCache(); + ship->UpdateViewport(true, true); + break; + } + + case VEH_ROAD: + SetWindowClassesDirty(WC_ROADVEH_LIST); + break; + + case VEH_AIRCRAFT: + SetWindowClassesDirty(WC_AIRCRAFT_LIST); + break; + + default: NOT_REACHED(); + } + + DepotID depot_id = GetDepotIndex(v->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, depot_id); + SetWindowDirty(WC_VEHICLE_VIEW, v->index); + + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); + + VehicleServiceInDepot(v); + + /* After a vehicle trigger, the graphics and properties of the vehicle could change. */ + TriggerVehicle(v, VEHICLE_TRIGGER_DEPOT); + v->MarkDirty(); + + if (v->current_order.IsType(OT_GOTO_DEPOT)) { + SetWindowDirty(WC_VEHICLE_VIEW, v->index); + + const Order *real_order = v->GetOrder(v->cur_real_order_index); + Order t = v->current_order; + v->current_order.MakeDummy(); + + /* Test whether we are heading for this depot. If not, do nothing. + * Note: The target depot for nearest-/manual-depot-orders is only updated on junctions, but we want to accept every depot. */ + if ((t.GetDepotOrderType() & ODTFB_PART_OF_ORDERS) && + real_order != nullptr && !(real_order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) && + t.GetDestination() != GetDepotIndex(v->tile)) { + /* We are heading for another depot, keep driving. */ + return; + } + + if (t.IsRefit()) { + Backup cur_company(_current_company, v->owner); + CommandCost cost = std::get<0>(Command::Do(DC_EXEC, v->index, t.GetRefitCargo(), 0xFF, false, false, 0)); + cur_company.Restore(); + + if (cost.Failed()) { + _vehicles_to_autoreplace[v->index] = false; + if (v->owner == _local_company) { + /* Notify the user that we stopped the vehicle */ + SetDParam(0, v->index); + AddVehicleAdviceNewsItem(STR_NEWS_ORDER_REFIT_FAILED, v->index); + } + } else if (cost.GetCost() != 0) { + v->profit_this_year -= cost.GetCost() << 8; + if (v->owner == _local_company) { + ShowCostOrIncomeAnimation(v->x_pos, v->y_pos, v->z_pos, cost.GetCost()); + } + } + } + + if (t.GetDepotOrderType() & ODTFB_PART_OF_ORDERS) { + /* Part of orders */ + v->DeleteUnreachedImplicitOrders(); + UpdateVehicleTimetable(v, true); + v->IncrementImplicitOrderIndex(); + } + if (t.GetDepotActionType() & ODATFB_HALT) { + /* Vehicles are always stopped on entering depots. Do not restart this one. */ + _vehicles_to_autoreplace[v->index] = false; + /* Invalidate last_loading_station. As the link from the station + * before the stop to the station after the stop can't be predicted + * we shouldn't construct it when the vehicle visits the next stop. */ + v->last_loading_station = INVALID_STATION; + if (v->owner == _local_company) { + SetDParam(0, v->index); + AddVehicleAdviceNewsItem(STR_NEWS_TRAIN_IS_WAITING + v->type, v->index); + } + AI::NewEvent(v->owner, new ScriptEventVehicleWaitingInDepot(v->index)); + } + } +} + /** * Check if the vehicle needs to go to a depot in near future (if a opportunity presents itself) for service or replacement. * @@ -297,7 +408,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 @@ -556,6 +667,24 @@ CommandCost EnsureNoVehicleOnGround(TileIndex tile) return CommandCost(); } +/** + * Ensure there is no visible vehicle at the ground at the given position. + * @param tile Position to examine. + * @return Succeeded command (ground is free) or failed command (a visible vehicle is found). + */ +CommandCost EnsureNoVisibleVehicleOnGround(TileIndex tile) +{ + int z = GetTileMaxPixelZ(tile); + + /* Value v is not safe in MP games, however, it is used to generate a local + * error message only (which may be different for different machines). + * Such a message does not affect MP synchronisation. + */ + Vehicle *v = VehicleFromPos(tile, &z, &EnsureNoVehicleProcZ, true); + if (v != nullptr && (v->vehstatus & VS_HIDDEN) == 0) return_cmd_error(STR_ERROR_TRAIN_IN_THE_WAY + v->type); + return CommandCost(); +} + /** Procedure called for every vehicle found in tunnel/bridge in the hash map */ static Vehicle *GetVehicleTunnelBridgeProc(Vehicle *v, void *data) { @@ -687,13 +816,6 @@ void ResetVehicleColourMap() for (Vehicle *v : Vehicle::Iterate()) { v->colourmap = PAL_NONE; } } -/** - * List of vehicles that should check for autoreplace this tick. - * Mapping of vehicle -> leave depot immediately after autoreplace. - */ -using AutoreplaceMap = std::map; -static AutoreplaceMap _vehicles_to_autoreplace; - void InitializeVehicles() { _vehicles_to_autoreplace.clear(); @@ -790,6 +912,22 @@ void Vehicle::ShiftDates(TimerGameEconomy::Date interval) */ void Vehicle::HandlePathfindingResult(bool path_found) { + if (this->dest_tile != INVALID_TILE && IsDepotTypeTile(this->dest_tile, (TransportType)this->type) && IsDepotFullWithStoppedVehicles(this->dest_tile)) { + /* Vehicle cannot find a free depot. */ + /* Were we already lost? */ + if (HasBit(this->vehicle_flags, VF_PATHFINDER_LOST)) return; + + /* It is first time the problem occurred, set the "lost" flag. */ + SetBit(this->vehicle_flags, VF_PATHFINDER_LOST); + /* Notify user about the event. */ + AI::NewEvent(this->owner, new ScriptEventVehicleLost(this->index)); + if (_settings_client.gui.lost_vehicle_warn && this->owner == _local_company) { + SetDParam(0, this->index); + AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_CAN_T_FIND_FREE_DEPOT, this->index); + } + return; + } + if (path_found) { /* Route found, is the vehicle marked with "lost" flag? */ if (!HasBit(this->vehicle_flags, VF_PATHFINDER_LOST)) return; @@ -868,8 +1006,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()) { @@ -1078,7 +1216,12 @@ void CallVehicleTicks() /* Start vehicle if we stopped them in VehicleEnteredDepotThisTick() * We need to stop them between VehicleEnteredDepotThisTick() and here or we risk that * they are already leaving the depot again before being replaced. */ - if (it.second) v->vehstatus &= ~VS_STOPPED; + if (it.second) { + v->vehstatus &= ~VS_STOPPED; + } else if (IsExtendedDepotTile(v->tile)){ + if (v->type == VEH_TRAIN) FreeTrainTrackReservation(Train::From(v)); + UpdateExtendedDepotReservation(v, true); + } /* Store the position of the effect as the vehicle pointer will become invalid later */ int x = v->x_pos; @@ -1554,12 +1697,11 @@ 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); SetWindowClassesDirty(WC_TRAINS_LIST); - /* Clear path reservation */ - SetDepotReservation(t->tile, false); if (_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(t->tile); UpdateSignalsOnSegment(t->tile, INVALID_DIAGDIR, t->owner); @@ -1580,7 +1722,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 +1737,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 +1761,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_base.h b/src/vehicle_base.h index 65fac6e103..8b1afb4978 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -52,6 +52,7 @@ enum VehicleFlags { VF_PATHFINDER_LOST, ///< Vehicle's pathfinder is lost. VF_SERVINT_IS_CUSTOM, ///< Service interval is custom. VF_SERVINT_IS_PERCENT, ///< Service interval is percent. + VF_IS_SERVICING, ///< Vehicle is servicing. }; /** Bit numbers used to indicate which of the #NewGRFCache values are valid. */ @@ -313,23 +314,24 @@ public: * 0xff == reserved for another custom sprite */ uint8_t spritenum; - uint8_t x_extent; ///< x-extent of vehicle bounding box - uint8_t y_extent; ///< y-extent of vehicle bounding box - uint8_t z_extent; ///< z-extent of vehicle bounding box - int8_t x_bb_offs; ///< x offset of vehicle bounding box - int8_t y_bb_offs; ///< y offset of vehicle bounding box - int8_t x_offs; ///< x offset for vehicle sprite - int8_t y_offs; ///< y offset for vehicle sprite + uint8_t x_extent; ///< x-extent of vehicle bounding box + uint8_t y_extent; ///< y-extent of vehicle bounding box + uint8_t z_extent; ///< z-extent of vehicle bounding box + int8_t x_bb_offs; ///< x offset of vehicle bounding box + int8_t y_bb_offs; ///< y offset of vehicle bounding box + int8_t x_offs; ///< x offset for vehicle sprite + int8_t y_offs; ///< y offset for vehicle sprite EngineID engine_type; ///< The type of engine used for this vehicle. TextEffectID fill_percent_te_id; ///< a text-effect id to a loading indicator object UnitID unitnumber; ///< unit number, for display purposes only - uint16_t cur_speed; ///< current speed - uint8_t subspeed; ///< fractional speed - uint8_t acceleration; ///< used by train & aircraft - uint32_t motion_counter; ///< counter to occasionally play a vehicle sound. - uint8_t progress; ///< The percentage (if divided by 256) this vehicle already crossed the tile unit. + uint16_t cur_speed; ///< current speed + uint8_t subspeed; ///< fractional speed + uint8_t acceleration; ///< used by train & aircraft + uint32_t motion_counter; ///< counter to occasionally play a vehicle sound. + uint16_t wait_counter; ///< waiting ticks (servicing, waiting in front of a signal or forced proceeding) + uint8_t progress; ///< The percentage (if divided by 256) this vehicle already crossed the tile unit. uint8_t waiting_triggers; ///< Triggers to be yet matched before rerandomizing the random bits. uint16_t random_bits; ///< Bits used for randomized variational spritegroups. @@ -781,6 +783,12 @@ public: bool HandleBreakdown(); + bool IsServicing() const { return HasBit(this->vehicle_flags, VF_IS_SERVICING); } + + void StartService(); + bool ContinueServicing(); + void StopServicing(); + bool NeedsAutorenewing(const Company *c, bool use_renew_setting = true) const; bool NeedsServicing() const; diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index 6d22d56b0a..b2f94fdda5 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -37,6 +37,9 @@ #include "roadveh_cmd.h" #include "train_cmd.h" #include "ship_cmd.h" +#include "depot_base.h" +#include "train_placement.h" +#include "strings_func.h" #include #include @@ -178,7 +181,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()) { @@ -250,6 +253,9 @@ CommandCost CmdSellVehicle(DoCommandFlag flags, VehicleID v_id, bool sell_chain, ret = CommandCost(EXPENSES_NEW_VEHICLES, -front->value); if (flags & DC_EXEC) { + if (front->type == VEH_ROAD && IsExtendedDepot(v->tile) && (flags & DC_AUTOREPLACE) == 0) { + UpdateExtendedDepotReservation(v, false); + } if (front->IsPrimaryVehicle() && backup_order) OrderBackup::Backup(front, client_id); delete front; } @@ -522,6 +528,11 @@ std::tuple CmdRefitVehicle(DoCommandFla /* For ships and aircraft there is always only one. */ only_this |= front->type == VEH_SHIP || front->type == VEH_AIRCRAFT; + /* If it is a train on a depot, lift it. New length of the vehicle won't be checked. */ + Train *train = (v->type == VEH_TRAIN && IsDepotTile(front->tile)) ? Train::From(front) : nullptr; + TrainPlacement train_placement; + train_placement.LiftTrain(train, flags); + auto [cost, refit_capacity, mail_capacity, cargo_capacities] = RefitVehicle(v, only_this, num_vehicles, new_cid, new_subtype, flags, auto_refit); if (flags & DC_EXEC) { @@ -553,12 +564,15 @@ 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(); } + /* If it is a train on an extended depot, try placing it. */ + train_placement.PlaceTrain(train, flags); + return { cost, refit_capacity, mail_capacity, cargo_capacities }; } @@ -583,12 +597,32 @@ CommandCost CmdStartStopVehicle(DoCommandFlag flags, VehicleID veh_id, bool eval if (v->vehstatus & VS_CRASHED) return_cmd_error(STR_ERROR_VEHICLE_IS_DESTROYED); switch (v->type) { - case VEH_TRAIN: - if ((v->vehstatus & VS_STOPPED) && Train::From(v)->gcache.cached_power == 0) return_cmd_error(STR_ERROR_TRAIN_START_NO_POWER); + case VEH_TRAIN: { + Train *t = Train::From(v); + if ((v->vehstatus & VS_STOPPED) && t->gcache.cached_power == 0) return_cmd_error(STR_ERROR_TRAIN_START_NO_POWER); + + /* Train cannot leave until changing the depot. Stop the train and send a message. */ + if (!(flags & DC_AUTOREPLACE) && v->IsStoppedInDepot() && CheckIfTrainNeedsPlacement(t)) { + TrainPlacement train_placement; + if (!train_placement.CanFindAppropriatePlatform(t, (flags & DC_EXEC) != 0)) { + SetDParam(0, v->index); + return_cmd_error(STR_ERROR_CAN_T_START_PLATFORM_TYPE + train_placement.info - PI_FAILED_PLATFORM_TYPE); + } + } break; + } case VEH_SHIP: + break; case VEH_ROAD: + if ((v->vehstatus & VS_STOPPED) && !(flags & DC_AUTOREPLACE) && v->IsStoppedInDepot()) { + Depot *dep = Depot:: GetByTile(v->tile); + + /* Check that the vehicle can drive on some tile of the depot */ + RoadType rt = Engine::Get(v->engine_type)->u.road.roadtype; + const RoadTypeInfo *rti = GetRoadTypeInfo(rt); + if ((dep->r_types.road_types & rti->powered_roadtypes) == 0) return_cmd_error(STR_ERROR_ROAD_VEHICLE_START_NO_POWER); + } break; case VEH_AIRCRAFT: { @@ -637,9 +671,14 @@ CommandCost CmdStartStopVehicle(DoCommandFlag flags, VehicleID veh_id, bool eval /* Unbunching data is no longer valid. */ v->ResetDepotUnbunching(); + if (v->type == VEH_TRAIN && v->IsInDepot() && IsExtendedDepotTile(v->tile)) { + if ((v->vehstatus & VS_STOPPED) != 0) FreeTrainTrackReservation(Train::From(v)); + UpdateExtendedDepotReservation(v, true); + } + 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 +706,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) { @@ -676,7 +715,14 @@ CommandCost CmdMassStartStopVehicle(DoCommandFlag flags, TileIndex tile, bool do if (!vehicle_list_window && !v->IsChainInDepot()) continue; /* Just try and don't care if some vehicle's can't be stopped. */ - Command::Do(flags, v->index, false); + CommandCost ret = Command::Do(flags, v->index, false); + if (ret.Failed()) { + if (ret.GetErrorMessage() == STR_ERROR_CAN_T_START_PLATFORM_TYPE || + ret.GetErrorMessage() == STR_ERROR_CAN_T_START_PLATFORM_LONG) { + SetDParam(0, v->index); + return ret; + } + } } return CommandCost(); @@ -699,7 +745,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 +778,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 */ @@ -1130,3 +1176,85 @@ CommandCost CmdChangeServiceInt(DoCommandFlag flags, VehicleID veh_id, uint16_t return CommandCost(); } + +const uint16_t DEFAULT_SERVICE_TIME = 1 << 7; +const uint16_t TRAIN_SERVICE_TIME = 1 << 8; + +/** + * A vehicle that entered an extended depot, starts servicing. + */ +void Vehicle::StartService() +{ + assert(IsExtendedDepotTile(this->tile)); + + switch (this->type) { + case VEH_AIRCRAFT: + case VEH_ROAD: + case VEH_SHIP: { + this->wait_counter = DEFAULT_SERVICE_TIME; + break; + } + + case VEH_TRAIN: { + this->wait_counter = TRAIN_SERVICE_TIME; + break; + } + + default: NOT_REACHED(); + } + + this->cur_speed = 0; + this->subspeed = 0; + + SetBit(this->vehicle_flags, VF_IS_SERVICING); + SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP); + assert(this->fill_percent_te_id == INVALID_TE_ID); + this->fill_percent_te_id = ShowFillingPercent(this->x_pos, this->y_pos, this->z_pos + 20, 0, STR_SERVICING_INDICATOR); +} + +/** + * Check if vehicle is servicing. + * If it is servicing, decrease time till finishing the servicing. + * It services the vehicle when servicing time ends. + * @return true if the vehicle is still servicing, false if it is not servicing. + */ +bool Vehicle::ContinueServicing() +{ + if (!HasBit(this->vehicle_flags, VF_IS_SERVICING)) return false; + + /* Update text effect every 16 ticks. */ + if ((this->wait_counter & 15) == 0) { + uint8_t percent; + uint16_t max = this->type == VEH_TRAIN ? TRAIN_SERVICE_TIME : DEFAULT_SERVICE_TIME; + /* Return the percentage */ + if ((max - this->wait_counter) * 2 < max) { + /* Less than 50%; round up, so that 0% means really empty. */ + percent = (uint8_t)CeilDiv((max - this->wait_counter) * 100, max); + } else { + /* More than 50%; round down, so that 100% means really full. */ + percent = (uint8_t)(((max - this->wait_counter) * 100) / max); + } + + if (this->fill_percent_te_id == INVALID_TE_ID) { + this->fill_percent_te_id = ShowFillingPercent(this->x_pos, this->y_pos, this->z_pos + 20, percent, STR_SERVICING_INDICATOR); + } else { + UpdateFillingPercent(this->fill_percent_te_id, percent, STR_SERVICING_INDICATOR); + } + } + + if (this->wait_counter--) return true; + + VehicleServiceInExtendedDepot(this); + this->StopServicing(); + return false; +} + +void Vehicle::StopServicing() +{ + this->wait_counter = 0; + + /* End servicing. */ + ClrBit(this->vehicle_flags, VF_IS_SERVICING); + HideFillingPercent(&this->fill_percent_te_id); + InvalidateWindowData(WC_VEHICLE_VIEW, this->index); +} diff --git a/src/vehicle_func.h b/src/vehicle_func.h index 8047a1952e..d92b6b1703 100644 --- a/src/vehicle_func.h +++ b/src/vehicle_func.h @@ -40,6 +40,7 @@ bool IsValidImageIndex(uint8_t image_index); typedef Vehicle *VehicleFromPosProc(Vehicle *v, void *data); void VehicleServiceInDepot(Vehicle *v); +void VehicleServiceInExtendedDepot(Vehicle *v); uint CountVehiclesInChain(const Vehicle *v); void FindVehicleOnPos(TileIndex tile, void *data, VehicleFromPosProc *proc); void FindVehicleOnPosXY(int x, int y, void *data, VehicleFromPosProc *proc); @@ -164,6 +165,7 @@ inline StringID GetCmdSendToDepotMsg(const BaseVehicle *v) } CommandCost EnsureNoVehicleOnGround(TileIndex tile); +CommandCost EnsureNoVisibleVehicleOnGround(TileIndex tile); CommandCost EnsureNoTrainOnTrackBits(TileIndex tile, TrackBits track_bits); bool CanVehicleUseStation(EngineID engine_type, const struct Station *st); diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 2a43b351a3..ad00066fb7 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); } @@ -3117,6 +3111,8 @@ public: } else { // no train str = STR_VEHICLE_STATUS_STOPPED; } + } else if (HasBit(v->vehicle_flags, VF_IS_SERVICING)) { + str = STR_VEHICLE_STATUS_SERVICING; } else if (v->IsInDepot() && v->IsWaitingForUnbunching()) { str = STR_VEHICLE_STATUS_WAITING_UNBUNCHING; } else if (v->type == VEH_TRAIN && HasBit(Train::From(v)->flags, VRF_TRAIN_STUCK) && !v->current_order.IsType(OT_LOADING)) { @@ -3141,7 +3137,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..2bee590429 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -66,6 +66,8 @@ #include "viewport_func.h" #include "station_base.h" #include "waypoint_base.h" +#include "depot_base.h" +#include "depot_func.h" #include "town.h" #include "signs_base.h" #include "signs_func.h" @@ -90,6 +92,7 @@ #include "network/network_func.h" #include "framerate_type.h" #include "viewport_cmd.h" +#include "depot_map.h" #include #include @@ -1002,6 +1005,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 +1014,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; @@ -1360,12 +1368,14 @@ static void ViewportAddKdtreeSigns(DrawPixelInfo *dpi) bool show_waypoints = HasBit(_display_opt, DO_SHOW_WAYPOINT_NAMES) && _game_mode != GM_MENU; bool show_towns = HasBit(_display_opt, DO_SHOW_TOWN_NAMES) && _game_mode != GM_MENU; bool show_signs = HasBit(_display_opt, DO_SHOW_SIGNS) && !IsInvisibilitySet(TO_SIGNS); + bool show_depotsigns = _game_mode != GM_MENU; bool show_competitors = HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS); /* Collect all the items first and draw afterwards, to ensure layering */ std::vector stations; std::vector towns; std::vector signs; + std::vector depots; _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) { switch (item.type) { @@ -1409,6 +1419,19 @@ static void ViewportAddKdtreeSigns(DrawPixelInfo *dpi) break; } + case ViewportSignKdtreeItem::VKI_DEPOT: { + if (!show_depotsigns) break; + const Depot *depot = Depot::Get(item.id.depot); + + /* Only show depot name after the depot is removed. */ + if (depot->IsInUse()) break; + /* Don't draw if depot is owned by another company and competitor signs are hidden. */ + if (!show_competitors && _local_company != depot->owner) break; + + depots.push_back(depot); + break; + } + default: NOT_REACHED(); } @@ -1435,6 +1458,12 @@ static void ViewportAddKdtreeSigns(DrawPixelInfo *dpi) (si->owner == OWNER_NONE) ? COLOUR_GREY : (si->owner == OWNER_DEITY ? INVALID_COLOUR : _company_colours[si->owner])); } + for (const auto *d : depots) { + SetDParam(0, d->veh_type); + SetDParam(1, d->index); + ViewportAddString(dpi, ZOOM_LVL_OUT_4X, &d->sign, STR_VIEWPORT_DEPOT, STR_VIEWPORT_DEPOT_TINY, STR_NULL, COLOUR_GREY); + } + for (const auto *st : stations) { SetDParam(0, st->index); SetDParam(1, st->facilities); @@ -2231,6 +2260,7 @@ static bool CheckClickOnViewportSign(const Viewport *vp, int x, int y) BaseStation *st = nullptr, *last_st = nullptr; Town *t = nullptr, *last_t = nullptr; Sign *si = nullptr, *last_si = nullptr; + Depot *dep = nullptr, *last_dep = nullptr; /* See ViewportAddKdtreeSigns() for details on the search logic */ _viewport_sign_kdtree.FindContained(search_rect.left, search_rect.top, search_rect.right, search_rect.bottom, [&](const ViewportSignKdtreeItem & item) { @@ -2262,6 +2292,12 @@ static bool CheckClickOnViewportSign(const Viewport *vp, int x, int y) if (CheckClickOnViewportSign(vp, x, y, &si->sign)) last_si = si; break; + case ViewportSignKdtreeItem::VKI_DEPOT: + dep = Depot::Get(item.id.depot); + if (!show_competitors && _local_company != st->owner && st->owner != OWNER_NONE) break; + if (CheckClickOnViewportSign(vp, x, y, &dep->sign)) last_dep = dep; + break; + default: NOT_REACHED(); } @@ -2281,6 +2317,9 @@ static bool CheckClickOnViewportSign(const Viewport *vp, int x, int y) } else if (last_si != nullptr) { HandleClickOnSign(last_si); return true; + } else if (last_dep != nullptr) { + ShowDepotWindow(last_dep->index); + return true; } else { return false; } @@ -2304,6 +2343,23 @@ ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeStation(StationID id) return item; } +ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeDepot(DepotID id) +{ + ViewportSignKdtreeItem item; + item.type = VKI_DEPOT; + item.id.depot = id; + + const Depot *depot = Depot::Get(id); + + item.center = depot->sign.center; + item.top = depot->sign.top; + + /* Assume the sign can be a candidate for drawing, so measure its width */ + _viewport_sign_maxwidth = std::max(_viewport_sign_maxwidth, depot->sign.width_normal); + + return item; +} + ViewportSignKdtreeItem ViewportSignKdtreeItem::MakeWaypoint(StationID id) { ViewportSignKdtreeItem item; @@ -2379,6 +2435,11 @@ void RebuildViewportKdtree() if (sign->sign.kdtree_valid) items.push_back(ViewportSignKdtreeItem::MakeSign(sign->index)); } + for (const Depot *dep : Depot::Iterate()) { + if (dep->IsInUse()) continue; + items.push_back(ViewportSignKdtreeItem::MakeDepot(dep->index)); + } + _viewport_sign_kdtree.Build(items.begin(), items.end()); } @@ -2674,6 +2735,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 +2829,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 +3309,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 +3322,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 +3378,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 +3667,13 @@ void MarkCatchmentTilesDirty() } MarkWholeScreenDirty(); } + if (_viewport_highlight_depot != INVALID_DEPOT) { + Depot *dep = Depot::Get(_viewport_highlight_depot); + if (!dep->IsInUse()) { + _viewport_highlight_depot = INVALID_DEPOT; + } + MarkWholeScreenDirty(); + } } static void SetWindowDirtyForViewportCatchment() @@ -3596,6 +3681,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 +3690,7 @@ static void ClearViewportCatchment() _viewport_highlight_station = nullptr; _viewport_highlight_waypoint = nullptr; _viewport_highlight_town = nullptr; + _viewport_highlight_depot = INVALID_DEPOT; } /** @@ -3665,3 +3752,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_kdtree.h b/src/viewport_kdtree.h index 3c2f49c2e4..333c3685fc 100644 --- a/src/viewport_kdtree.h +++ b/src/viewport_kdtree.h @@ -22,12 +22,14 @@ struct ViewportSignKdtreeItem { VKI_WAYPOINT, VKI_TOWN, VKI_SIGN, + VKI_DEPOT, }; ItemType type; union { StationID station; TownID town; SignID sign; + DepotID depot; } id; int32_t center; int32_t top; @@ -43,6 +45,8 @@ struct ViewportSignKdtreeItem { return this->id.town == other.id.town; case VKI_SIGN: return this->id.sign == other.id.sign; + case VKI_DEPOT: + return this->id.depot == other.id.depot; default: NOT_REACHED(); } @@ -59,6 +63,8 @@ struct ViewportSignKdtreeItem { return this->id.town < other.id.town; case VKI_SIGN: return this->id.sign < other.id.sign; + case VKI_DEPOT: + return this->id.depot < other.id.depot; default: NOT_REACHED(); } @@ -68,6 +74,7 @@ struct ViewportSignKdtreeItem { static ViewportSignKdtreeItem MakeWaypoint(StationID id); static ViewportSignKdtreeItem MakeTown(TownID id); static ViewportSignKdtreeItem MakeSign(SignID id); + static ViewportSignKdtreeItem MakeDepot(DepotID id); }; inline int32_t Kdtree_ViewportSignXYFunc(const ViewportSignKdtreeItem &item, int dim) diff --git a/src/viewport_type.h b/src/viewport_type.h index 0fde051f38..a582e99359 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 + VPM_LIMITED_X_FIXED_Y = 10, ///< Drag only in X axis with limited size and a fixed value for y 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 5a0a94d172..92a8d179b5 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -40,6 +40,9 @@ #include "water_cmd.h" #include "landscape_cmd.h" #include "pathfinder/water_regions.h" +#include "train.h" +#include "platform_func.h" +#include "pbs.h" #include "table/strings.h" @@ -94,66 +97,80 @@ 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 extended whether to build an extended depot + * @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, bool extended, DepotID join_to, TileIndex end_tile) { + if (Company::IsValidHumanID(_current_company) && !HasBit(_settings_game.depot.water_depot_types, extended)) { + return_cmd_error(STR_ERROR_DEPOT_TYPE_NOT_AVAILABLE); + } + 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, extended, 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 +292,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 +302,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]); @@ -974,7 +992,7 @@ static void GetTileDesc_Water(TileIndex tile, TileDesc *td) case WATER_TILE_COAST: td->str = STR_LAI_WATER_DESCRIPTION_COAST_OR_RIVERBANK; break; case WATER_TILE_LOCK : td->str = STR_LAI_WATER_DESCRIPTION_LOCK; break; case WATER_TILE_DEPOT: - td->str = STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT; + td->str = IsExtendedDepot(tile) ? STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT_EXTENDED : STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT; td->build_date = Depot::GetByTile(tile)->build_date; break; default: NOT_REACHED(); @@ -1058,16 +1076,25 @@ static void FloodVehicles(TileIndex tile) return; } - if (!IsBridgeTile(tile)) { + if (IsBridgeTile(tile)) { + TileIndex end = GetOtherBridgeEnd(tile); + z = GetBridgePixelHeight(tile); + + FindVehicleOnPos(tile, &z, &FloodVehicleProc); + FindVehicleOnPos(end, &z, &FloodVehicleProc); + } else if (IsExtendedRailDepotTile(tile)) { + /* Free reserved path. */ + if (HasDepotReservation(tile)) { + Train *v = GetTrainForReservation(tile, GetRailDepotTrack(tile)); + if (v != nullptr) FreeTrainTrackReservation(v); + } + /* Crash trains on platform. */ + for (TileIndex t : GetPlatformTileArea(tile)) { + FindVehicleOnPos(t, &z, &FloodVehicleProc); + } + } else { FindVehicleOnPos(tile, &z, &FloodVehicleProc); - return; } - - TileIndex end = GetOtherBridgeEnd(tile); - z = GetBridgePixelHeight(tile); - - FindVehicleOnPos(tile, &z, &FloodVehicleProc); - FindVehicleOnPos(end, &z, &FloodVehicleProc); } /** @@ -1334,7 +1361,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..02efc043ce 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, bool extended, 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/water_map.h b/src/water_map.h index 116a37f228..3cfdac071a 100644 --- a/src/water_map.h +++ b/src/water_map.h @@ -17,12 +17,14 @@ * Bit field layout of m5 for water tiles. */ enum WaterTileTypeBitLayout { - WBL_TYPE_BEGIN = 4, ///< Start of the 'type' bitfield. - WBL_TYPE_COUNT = 4, ///< Length of the 'type' bitfield. + WBL_TYPE_BEGIN = 6, ///< Start of the 'type' bitfield. + WBL_TYPE_COUNT = 2, ///< Length of the 'type' bitfield. WBL_TYPE_NORMAL = 0x0, ///< Clear water or coast ('type' bitfield). WBL_TYPE_LOCK = 0x1, ///< Lock ('type' bitfield). - WBL_TYPE_DEPOT = 0x8, ///< Depot ('type' bitfield). + WBL_TYPE_DEPOT = 0x2, ///< Depot ('type' bitfield). + + WBL_DEPOT_EXTENDED = 5, ///< Bit for the standard/extended depot flag. WBL_COAST_FLAG = 0, ///< Flag for coast. @@ -78,6 +80,16 @@ enum LockPart { bool IsPossibleDockingTile(Tile t); +/** + * Get the type of water tile: clear, lock or depot. + * @param t Water tile to query. + * @return WBL_TYPE_NORMAL, WBL_TYPE_LOCK or WBL_TYPE_DEPOT. + */ +static inline WaterTileTypeBitLayout GetWaterTileClass(Tile t) { + assert(IsTileType(t, MP_WATER)); + return (WaterTileTypeBitLayout)GB(t.m5(), WBL_TYPE_BEGIN, WBL_TYPE_COUNT); +} + /** * Get the water tile type at a tile. * @param t Water tile to query. @@ -87,7 +99,7 @@ inline WaterTileType GetWaterTileType(Tile t) { assert(IsTileType(t, MP_WATER)); - switch (GB(t.m5(), WBL_TYPE_BEGIN, WBL_TYPE_COUNT)) { + switch (GetWaterTileClass(t)) { case WBL_TYPE_NORMAL: return HasBit(t.m5(), WBL_COAST_FLAG) ? WATER_TILE_COAST : WATER_TILE_CLEAR; case WBL_TYPE_LOCK: return WATER_TILE_LOCK; case WBL_TYPE_DEPOT: return WATER_TILE_DEPOT; @@ -224,7 +236,8 @@ inline bool IsCoastTile(Tile t) */ inline bool IsShipDepot(Tile t) { - return GetWaterTileType(t) == WATER_TILE_DEPOT; + assert(IsTileType(t, MP_WATER)); + return GetWaterTileClass(t) == WBL_TYPE_DEPOT; } /** @@ -305,7 +318,8 @@ inline TileIndex GetShipDepotNorthTile(Tile t) */ inline bool IsLock(Tile t) { - return GetWaterTileType(t) == WATER_TILE_LOCK; + assert(IsTileType(t, MP_WATER)); + return GetWaterTileClass(t) == WBL_TYPE_LOCK; } /** @@ -452,11 +466,12 @@ inline void MakeCanal(Tile t, Owner o, uint8_t random_bits) * @param t Tile to place the ship depot section. * @param o Owner of the depot. * @param did Depot ID. + * @param extended True if building an extended depot. * @param part Depot part (either #DEPOT_PART_NORTH or #DEPOT_PART_SOUTH). * @param a Axis of the depot. * @param original_water_class Original water class. */ -inline void MakeShipDepot(Tile t, Owner o, DepotID did, DepotPart part, Axis a, WaterClass original_water_class) +inline void MakeShipDepot(Tile t, Owner o, DepotID did, bool extended, DepotPart part, Axis a, WaterClass original_water_class) { SetTileType(t, MP_WATER); SetTileOwner(t, o); @@ -465,7 +480,7 @@ inline void MakeShipDepot(Tile t, Owner o, DepotID did, DepotPart part, Axis a, t.m2() = did; t.m3() = 0; t.m4() = 0; - t.m5() = WBL_TYPE_DEPOT << WBL_TYPE_BEGIN | part << WBL_DEPOT_PART | a << WBL_DEPOT_AXIS; + t.m5() = WBL_TYPE_DEPOT << WBL_TYPE_BEGIN | extended << WBL_DEPOT_EXTENDED | part << WBL_DEPOT_PART | a << WBL_DEPOT_AXIS; SB(t.m6(), 2, 4, 0); t.m7() = 0; } diff --git a/src/waypoint_base.h b/src/waypoint_base.h index cbf2e1e608..a7017d9acb 100644 --- a/src/waypoint_base.h +++ b/src/waypoint_base.h @@ -45,16 +45,6 @@ struct Waypoint final : SpecializedStation { void GetTileArea(TileArea *ta, StationType type) const override; - uint GetPlatformLength(TileIndex, DiagDirection) const override - { - return 1; - } - - uint GetPlatformLength(TileIndex) const override - { - return 1; - } - /** * Is this a single tile waypoint? * @return true if it is. 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/widgets/dock_widget.h b/src/widgets/dock_widget.h index 0a1f480eb6..84f39589c0 100644 --- a/src/widgets/dock_widget.h +++ b/src/widgets/dock_widget.h @@ -22,7 +22,8 @@ enum DockToolbarWidgets : WidgetID { WID_DT_CANAL, ///< Build canal button. WID_DT_LOCK, ///< Build lock button. WID_DT_DEMOLISH, ///< Demolish aka dynamite button. - WID_DT_DEPOT, ///< Build depot button. + WID_DT_DEPOT, ///< Build standard depot button. + WID_DT_EXTENDED_DEPOT, ///< Build extended depot button. WID_DT_STATION, ///< Build station button. WID_DT_BUOY, ///< Build buoy button. WID_DT_RIVER, ///< Build river button (in scenario editor). diff --git a/src/widgets/rail_widget.h b/src/widgets/rail_widget.h index f90280c52b..b7358c5152 100644 --- a/src/widgets/rail_widget.h +++ b/src/widgets/rail_widget.h @@ -13,21 +13,22 @@ /** Widgets of the #BuildRailToolbarWindow class. */ enum RailToolbarWidgets : WidgetID { /* Name starts with RA instead of R, because of collision with RoadToolbarWidgets */ - WID_RAT_CAPTION, ///< Caption of the window. - WID_RAT_BUILD_NS, ///< Build rail along the game view Y axis. - WID_RAT_BUILD_X, ///< Build rail along the game grid X axis. - WID_RAT_BUILD_EW, ///< Build rail along the game view X axis. - WID_RAT_BUILD_Y, ///< Build rail along the game grid Y axis. - WID_RAT_AUTORAIL, ///< Autorail tool. - WID_RAT_DEMOLISH, ///< Destroy something with dynamite! - WID_RAT_BUILD_DEPOT, ///< Build a depot. - WID_RAT_BUILD_WAYPOINT, ///< Build a waypoint. - WID_RAT_BUILD_STATION, ///< Build a station. - WID_RAT_BUILD_SIGNALS, ///< Build signals. - WID_RAT_BUILD_BRIDGE, ///< Build a bridge. - WID_RAT_BUILD_TUNNEL, ///< Build a tunnel. - WID_RAT_REMOVE, ///< Bulldozer to remove rail. - WID_RAT_CONVERT_RAIL, ///< Convert other rail to this type. + WID_RAT_CAPTION, ///< Caption of the window. + WID_RAT_BUILD_NS, ///< Build rail along the game view Y axis. + WID_RAT_BUILD_X, ///< Build rail along the game grid X axis. + WID_RAT_BUILD_EW, ///< Build rail along the game view X axis. + WID_RAT_BUILD_Y, ///< Build rail along the game grid Y axis. + WID_RAT_AUTORAIL, ///< Autorail tool. + WID_RAT_DEMOLISH, ///< Destroy something with dynamite! + WID_RAT_BUILD_DEPOT, ///< Build a depot. + WID_RAT_BUILD_EXTENDED_DEPOT, ///< Build an extended depot. + WID_RAT_BUILD_WAYPOINT, ///< Build a waypoint. + WID_RAT_BUILD_STATION, ///< Build a station. + WID_RAT_BUILD_SIGNALS, ///< Build signals. + WID_RAT_BUILD_BRIDGE, ///< Build a bridge. + WID_RAT_BUILD_TUNNEL, ///< Build a tunnel. + WID_RAT_REMOVE, ///< Bulldozer to remove rail. + WID_RAT_CONVERT_RAIL, ///< Convert other rail to this type. INVALID_WID_RAT = -1, }; diff --git a/src/widgets/road_widget.h b/src/widgets/road_widget.h index 679545728a..2fbf3c6573 100644 --- a/src/widgets/road_widget.h +++ b/src/widgets/road_widget.h @@ -19,6 +19,7 @@ enum RoadToolbarWidgets : WidgetID { WID_ROT_AUTOROAD, ///< Autorail. WID_ROT_DEMOLISH, ///< Demolish. WID_ROT_DEPOT, ///< Build depot. + WID_ROT_EXTENDED_DEPOT, ///< Build extended depot. WID_ROT_BUILD_WAYPOINT, ///< Build waypoint. WID_ROT_BUS_STATION, ///< Build bus station. WID_ROT_TRUCK_STATION, ///< Build truck station. diff --git a/src/window_gui.h b/src/window_gui.h index 1c5b7d8d5c..a082e141f0 100644 --- a/src/window_gui.h +++ b/src/window_gui.h @@ -336,6 +336,11 @@ public: template inline NWID *GetWidget(WidgetID widnum); + inline bool HasWidget(WidgetID widnum) const + { + return this->GetWidget(widnum) != nullptr; + } + const Scrollbar *GetScrollbar(WidgetID widnum) const; Scrollbar *GetScrollbar(WidgetID widnum); diff --git a/src/window_type.h b/src/window_type.h index 0896d5ff6f..3609e3b5f0 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 + * - #INVALID_DEPOT - VehicleType = #BuildVehicleWidgets + * - #DepotID = #BuildVehicleWidgets */ WC_BUILD_VEHICLE,