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
- m2: Depot index
- m5 bits 1..0: exit towards
@@ -547,6 +547,11 @@
- m5 bit 4: pbs reservation state
+ - m5 bit 5 clear: standard depot
+ - m5 bit 5 set: extended depot
+
+ - m4 bits 6..7: depot reservation state
+
@@ -666,7 +671,7 @@
- m1 bits 4..0: owner of the depot
- m2: Depot index
- - m5 bits 1..0: exit towards:
+
- m6 bits 6..7: exit towards:
+ - m3 bits 0..3: road layout road type 1 (tram)
+ - m5 bits 0..3: road layout road type 0 (normal road)
+ - m5 bit 5 clear: standard depot
+ - m5 bit 5 set: extended depot
+
+ - m4 bits 6..7: depot reservation state towards north direction
+ - m6 bits 4..5: depot reservation state towards south direction
+
+
- m7 bits 4..0: owner of the road type 0 (normal road)
@@ -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
+
+ - m4 bits 6..7: depot reservation state (for depots only)
+
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,