From 719376c219862afc227fe62bba9021b17dadd129 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Mon, 19 Feb 2024 21:25:38 +0100 Subject: [PATCH 01/78] Change: Add a depot for each airport that has a hangar. --- src/saveload/afterload.cpp | 59 ++++++++++++++++--------- src/saveload/compat/station_sl_compat.h | 1 + src/saveload/saveload.cpp | 6 +++ src/saveload/saveload.h | 2 + src/saveload/station_sl.cpp | 1 + src/station.cpp | 24 ++++++++++ src/station_base.h | 5 +++ src/station_cmd.cpp | 6 +++ 8 files changed, 82 insertions(+), 22 deletions(-) diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 06102930b7..be048f33c8 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -2425,28 +2425,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 +2770,43 @@ 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); + } + } + } + + /* In old versions it was possible to remove an airport while a plane was + * taking off or landing. This gives all kind of problems when building + * another airport in the same station so we don't allow that anymore. + * For old savegames with such aircraft we just throw them in the air and + * treat the aircraft like they were flying already. */ + if (IsSavegameVersionBefore(SLV_146)) { + for (Aircraft *v : Aircraft::Iterate()) { + if (!v->IsNormalAircraft()) continue; + Station *st = GetTargetAirportIfValid(v); + if (st == nullptr && v->state != FLYING) { + v->state = FLYING; + UpdateAircraftCache(v); + AircraftNextAirportPos_and_Order(v); + /* get aircraft back on running altitude */ + if ((v->vehstatus & VS_CRASHED) == 0) { + GetAircraftFlightLevelBounds(v, &v->z_pos, nullptr); + SetAircraftPosition(v, v->x_pos, v->y_pos, GetAircraftFlightLevel(v)); + } + } + } + } + /* This triggers only when old snow_lines were copied into the snow_line_height. */ if (IsSavegameVersionBefore(SLV_164) && _settings_game.game_creation.snow_line_height >= MIN_SNOWLINE_HEIGHT * TILE_HEIGHT) { _settings_game.game_creation.snow_line_height /= TILE_HEIGHT; diff --git a/src/saveload/compat/station_sl_compat.h b/src/saveload/compat/station_sl_compat.h index 1c24a8d5d9..e7b1dbab68 100644 --- a/src/saveload/compat/station_sl_compat.h +++ b/src/saveload/compat/station_sl_compat.h @@ -108,6 +108,7 @@ const SaveLoadCompat _station_normal_sl_compat[] = { SLC_VAR("airport.layout"), SLC_VAR("airport.flags"), SLC_VAR("airport.rotation"), + SLC_VAR("airport.hangar"), SLC_VAR("storage"), SLC_VAR("airport.psa"), SLC_VAR("indtype"), diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 7f04432298..8881e5c772 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 a820c7c4f1..9a19beddc1 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -383,6 +383,7 @@ 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. SL_MAX_VERSION, ///< Highest possible saveload version }; @@ -598,6 +599,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/station.cpp b/src/station.cpp index e0dab59067..ee1833963a 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -26,6 +26,7 @@ #include "core/random_func.hpp" #include "linkgraph/linkgraph.h" #include "linkgraph/linkgraphschedule.h" +#include "depot_base.h" #include "table/strings.h" @@ -727,6 +728,29 @@ 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)); + this->hangar->build_date = st->build_date; + this->hangar->town = st->town; +} + +/** + * Delete the hangar on the airport. + */ +void Airport::RemoveHangar() +{ + delete this->hangar; + this->hangar = nullptr; +} + bool StationCompare::operator() (const Station *lhs, const Station *rhs) const { return lhs->index < rhs->index; diff --git a/src/station_base.h b/src/station_base.h index 6dda3ca843..f5db44f23b 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -18,6 +18,7 @@ #include "linkgraph/linkgraph_type.h" #include "newgrf_storage.h" #include "bitmap_type.h" +#include "depot_type.h" static const uint8_t INITIAL_STATION_RATING = 175; static const uint8_t MAX_STATION_RATING = 255; @@ -294,6 +295,7 @@ struct Airport : public TileArea { uint8_t type; ///< Type of this airport, @see AirportTypes uint8_t layout; ///< Airport layout number. Direction rotation; ///< How this airport is rotated. + Depot *hangar; ///< The corresponding hangar of this airport, if any. PersistentStorage *psa; ///< Persistent storage for NewGRF airports. @@ -404,6 +406,9 @@ struct Airport : public TileArea { return num; } + void AddHangar(); + void RemoveHangar(); + private: /** * Retrieve hangar information of a hangar at a given tile. diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 0dd4edb779..f3cefb224e 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -67,6 +67,7 @@ #include "timer/timer_game_tick.h" #include "cheat_type.h" #include "road_func.h" +#include "depot_base.h" #include "widgets/station_widget.h" @@ -2539,6 +2540,8 @@ CommandCost CmdBuildAirport(DoCommandFlag flags, TileIndex tile, uint8_t airport /* Check if a valid, buildable airport was chosen for construction */ const AirportSpec *as = AirportSpec::Get(airport_type); + + if (!as->depots.empty() && !Depot::CanAllocateItem()) return CMD_ERROR; if (!as->IsAvailable() || layout >= as->layouts.size()) return CMD_ERROR; if (!as->IsWithinMapBounds(layout, tile)) return CMD_ERROR; @@ -2632,6 +2635,8 @@ CommandCost CmdBuildAirport(DoCommandFlag flags, TileIndex tile, uint8_t airport AirportTileAnimationTrigger(st, iter, AAT_BUILT); } + if (!as->depots.empty()) st->airport.AddHangar(); + UpdateAirplanesOnNewStation(st); Company::Get(st->owner)->infrastructure.airport++; @@ -2679,6 +2684,7 @@ static CommandCost RemoveAirport(TileIndex tile, DoCommandFlag flags) 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 From 9e7e84fd102ce6204d1b7b515590cad8f75c27b8 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Fri, 25 Aug 2023 11:39:01 +0200 Subject: [PATCH 02/78] Change: Go to hangar orders store the DepotID instead of the StationID. --- src/aircraft_cmd.cpp | 33 +++++------- src/depot_map.h | 11 ++-- src/order_backup.cpp | 8 +-- src/order_backup.h | 2 +- src/order_cmd.cpp | 90 ++++++++++++++++----------------- src/order_func.h | 3 +- src/order_gui.cpp | 6 +-- src/saveload/afterload.cpp | 29 +++++++++++ src/saveload/saveload.h | 2 + src/script/api/script_order.cpp | 7 +-- src/station.cpp | 15 ++++++ src/vehicle.cpp | 2 +- src/vehicle_gui.cpp | 3 +- 13 files changed, 123 insertions(+), 88 deletions(-) diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 33ab2eba38..975bc17b68 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); @@ -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/depot_map.h b/src/depot_map.h index 87bd915431..78a0f0273c 100644 --- a/src/depot_map.h +++ b/src/depot_map.h @@ -43,16 +43,21 @@ inline bool IsDepotTile(Tile tile) return IsRailDepotTile(tile) || IsRoadDepotTile(tile) || IsShipDepotTile(tile) || IsHangarTile(tile); } +extern DepotID GetHangarIndex(TileIndex t); + /** * Get the index of which depot is attached to the tile. * @param t the tile - * @pre IsRailDepotTile(t) || IsRoadDepotTile(t) || IsShipDepotTile(t) + * @pre IsDepotTile(t) * @return DepotID */ inline DepotID GetDepotIndex(Tile t) { - /* Hangars don't have a Depot class, thus store no DepotID. */ - assert(IsRailDepotTile(t) || IsRoadDepotTile(t) || IsShipDepotTile(t)); + assert(IsDepotTile(t)); + + /* Hangars don't store depot id on m2. */ + if (IsTileType(t, MP_STATION)) return GetHangarIndex(t); + return t.m2(); } diff --git a/src/order_backup.cpp b/src/order_backup.cpp index f696d8435d..9843bbf36f 100644 --- a/src/order_backup.cpp +++ b/src/order_backup.cpp @@ -246,18 +246,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..23832d59af 100644 --- a/src/order_backup.h +++ b/src/order_backup.h @@ -59,7 +59,7 @@ public: static void ClearGroup(GroupID group); static void ClearVehicle(const Vehicle *v); - static void RemoveOrder(OrderType type, DestinationID destination, bool hangar); + static void RemoveOrder(OrderType type, DestinationID destination); }; #endif /* ORDER_BACKUP_H */ diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 1d6743106e..070b0d84b1 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,40 +785,31 @@ CommandCost CmdInsertOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID se case OT_GOTO_DEPOT: { if ((new_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) == 0) { - if (v->type == VEH_AIRCRAFT) { - const Station *st = Station::GetIfValid(new_order.GetDestination()); + const Depot *dp = Depot::GetIfValid(new_order.GetDestination()); - if (st == nullptr) return CMD_ERROR; + if (dp == nullptr) return CMD_ERROR; - ret = CheckOwnership(st->owner); - if (ret.Failed()) return ret; + ret = CheckOwnership(GetTileOwner(dp->xy)); + if (ret.Failed()) return ret; - if (!CanVehicleUseStation(v, st) || !st->airport.HasHangar()) { - return CMD_ERROR; - } - } else { - const Depot *dp = Depot::GetIfValid(new_order.GetDestination()); + switch (v->type) { + case VEH_TRAIN: + if (!IsRailDepotTile(dp->xy)) return CMD_ERROR; + break; - if (dp == nullptr) return CMD_ERROR; + case VEH_ROAD: + if (!IsRoadDepotTile(dp->xy)) return CMD_ERROR; + break; - ret = CheckOwnership(GetTileOwner(dp->xy)); - if (ret.Failed()) return ret; + case VEH_SHIP: + if (!IsShipDepotTile(dp->xy)) return CMD_ERROR; + break; - 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; + case VEH_AIRCRAFT: + if (!CanVehicleUseStation(v, Station::GetByTile(dp->xy)) || !IsHangarTile(dp->xy)) return CMD_ERROR; + break; default: return CMD_ERROR; - } } } @@ -1780,24 +1793,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 +1806,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 +1840,7 @@ restart: } } - OrderBackup::RemoveOrder(type, destination, hangar); + OrderBackup::RemoveOrder(type, destination); } /** @@ -2054,7 +2053,8 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool v->SetDestTile(Depot::Get(order->GetDestination())->xy); } else { Aircraft *a = Aircraft::From(v); - DestinationID destination = a->current_order.GetDestination(); + DestinationID destination_depot = a->current_order.GetDestination(); + StationID destination = GetStationIndex(Depot::Get(destination_depot)->xy); if (a->targetairport != destination) { /* The aircraft is now heading for a different hangar than the next in the orders */ a->SetDestTile(a->GetOrderStationLocation(destination)); diff --git a/src/order_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/saveload/afterload.cpp b/src/saveload/afterload.cpp index be048f33c8..7f35427e0a 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -2785,6 +2785,35 @@ bool AfterLoadGame() } } + 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); + } + } + } + /* 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. diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 9a19beddc1..8251fc23ab 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -385,6 +385,8 @@ enum SaveLoadVersion : uint16_t { 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. + SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/script/api/script_order.cpp b/src/script/api/script_order.cpp index b68f7fd67a..5dcd39ac4e 100644 --- a/src/script/api/script_order.cpp +++ b/src/script/api/script_order.cpp @@ -245,18 +245,13 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr const Order *order = ::ResolveOrder(vehicle_id, order_position); if (order == nullptr || order->GetType() == OT_CONDITIONAL) return INVALID_TILE; - const Vehicle *v = ::Vehicle::Get(vehicle_id); switch (order->GetType()) { case OT_GOTO_DEPOT: { /* We don't know where the nearest depot is... (yet) */ if (order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) return INVALID_TILE; - if (v->type != VEH_AIRCRAFT) return ::Depot::Get(order->GetDestination())->xy; - /* Aircraft's hangars are referenced by StationID, not DepotID */ - const Station *st = ::Station::Get(order->GetDestination()); - if (!st->airport.HasHangar()) return INVALID_TILE; - return st->airport.GetHangarTile(0); + return ::Depot::Get(order->GetDestination())->xy; } case OT_GOTO_STATION: { diff --git a/src/station.cpp b/src/station.cpp index ee1833963a..0ee8f2a605 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -747,10 +747,25 @@ void Airport::AddHangar() */ void Airport::RemoveHangar() { + RemoveOrderFromAllVehicles(OT_GOTO_DEPOT, this->hangar->index); + + for (Aircraft *a : Aircraft::Iterate()) { + if (!a->IsNormalAircraft()) continue; + if (!a->current_order.IsType(OT_GOTO_DEPOT)) continue; + if (a->current_order.GetDestination() != this->hangar->index) continue; + a->current_order.MakeDummy(); + } + delete this->hangar; this->hangar = nullptr; } +DepotID GetHangarIndex(TileIndex t) { + assert(IsAirportTile(t)); + assert(Station::GetByTile(t)->airport.hangar != nullptr); + return Station::GetByTile(t)->airport.hangar->index; +} + bool StationCompare::operator() (const Station *lhs, const Station *rhs) const { return lhs->index < rhs->index; diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 9db7da2fa1..8a7a471e8a 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1619,7 +1619,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_gui.cpp b/src/vehicle_gui.cpp index 2a43b351a3..aaaf10f993 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" @@ -3141,7 +3142,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 From dc0bd387b6cc0a8313ff30182d6434aa8fb95661 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Fri, 25 Aug 2023 14:49:03 +0200 Subject: [PATCH 03/78] Change: Set the DepotID as the window number of WC_VEHICLE_DEPOT windows instead of a TileIndex. --- src/aircraft_cmd.cpp | 2 +- src/depot.cpp | 2 +- src/depot_cmd.cpp | 2 +- src/depot_func.h | 2 +- src/depot_gui.cpp | 97 +++++++++++++++++++++----------------------- src/group_cmd.cpp | 3 +- src/rail_cmd.cpp | 9 ++-- src/road_cmd.cpp | 8 ++-- src/roadveh_cmd.cpp | 2 +- src/ship_cmd.cpp | 5 ++- src/station_cmd.cpp | 5 +-- src/train_cmd.cpp | 20 +++++---- src/vehicle.cpp | 13 +++--- src/vehicle_cmd.cpp | 7 ++-- src/water_cmd.cpp | 2 +- src/window_type.h | 2 +- 16 files changed, 92 insertions(+), 89 deletions(-) diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 975bc17b68..1bac641367 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -1490,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); } diff --git a/src/depot.cpp b/src/depot.cpp index 13317e8a35..38330a8739 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -41,7 +41,7 @@ Depot::~Depot() 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); diff --git a/src/depot_cmd.cpp b/src/depot_cmd.cpp index 294de69e32..1e1700f6cb 100644 --- a/src/depot_cmd.cpp +++ b/src/depot_cmd.cpp @@ -68,7 +68,7 @@ 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); diff --git a/src/depot_func.h b/src/depot_func.h index 208e02110c..7b3b6a9eeb 100644 --- a/src/depot_func.h +++ b/src/depot_func.h @@ -13,7 +13,7 @@ #include "vehicle_type.h" #include "slope_func.h" -void ShowDepotWindow(TileIndex tile, VehicleType type); +void ShowDepotWindow(DepotID depot_id); void InitDepotWindowBlockSizes(); void DeleteDepotHighlightOfVehicle(const Vehicle *v); diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp index e62915fe2c..c2a6ebce1c 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -266,15 +266,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(GetDepotVehicleType(depot->xy))); this->sel = INVALID_VEHICLE; this->vehicle_over = INVALID_VEHICLE; this->generate_list = true; this->hovered_widget = -1; - this->type = type; + this->type = GetDepotVehicleType(depot->xy); this->num_columns = 1; // for non-trains this gets set in FinishInitNested() this->unitnumber_digits = 2; @@ -282,22 +284,22 @@ 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 = GetTileOwner(depot->xy); 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); this->Window::Close(); } @@ -426,7 +428,7 @@ struct DepotWindow : Window { if (widget != WID_D_CAPTION) return; SetDParam(0, this->type); - SetDParam(1, this->GetDepotIndex()); + SetDParam(1, (this->type == VEH_AIRCRAFT) ? GetStationIndex(Depot::Get(this->window_number)->xy) : this->window_number); } struct GetDepotVehiclePtData { @@ -711,7 +713,7 @@ struct DepotWindow : Window { 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); + BuildDepotVehicleList(this->type, Depot::Get(this->window_number)->xy, &this->vehicle_list, &this->wagon_list); this->generate_list = false; DepotSortList(&this->vehicle_list); @@ -742,8 +744,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 +760,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); @@ -766,7 +769,7 @@ struct DepotWindow : Window { case WID_D_BUILD: // Build vehicle ResetObjectToPlace(); - ShowBuildVehicleWindow(this->window_number, this->type); + ShowBuildVehicleWindow(Depot::Get(this->window_number)->xy, this->type); break; case WID_D_CLONE: // Clone button @@ -789,20 +792,20 @@ 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_STOP_ALL: case WID_D_START_ALL: { VehicleListIdentifier vli(VL_DEPOT_LIST, this->type, this->owner); - Command::Post(this->window_number, widget == WID_D_START_ALL, false, vli); + Command::Post(tile, widget == WID_D_START_ALL, false, vli); break; } @@ -810,7 +813,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 +824,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, Depot::Get(this->window_number)->xy); break; case WID_D_AUTOREPLACE: - Command::Post(this->window_number, this->type); + Command::Post(tile, this->type); break; } @@ -836,7 +839,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 +905,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 +1114,33 @@ struct DepotWindow : Window { return ES_NOT_HANDLED; } - - /** - * Gets the DepotID of the current window. - * In the case of airports, this is the station ID. - * @return Depot or station ID of this window. - */ - inline uint16_t GetDepotIndex() const - { - return (this->type == VEH_AIRCRAFT) ? ::GetStationIndex(this->window_number) : ::GetDepotIndex(this->window_number); - } }; static void DepotSellAllConfirmationCallback(Window *win, bool confirmed) { if (confirmed) { - DepotWindow *w = (DepotWindow*)win; - TileIndex tile = w->window_number; - VehicleType vehtype = w->type; - Command::Post(tile, vehtype); + assert(Depot::IsValidID(win->window_number)); + Depot *d = Depot::Get(win->window_number); + Command::Post(d->xy, GetDepotVehicleType(d->xy)); } } /** - * 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 (GetDepotVehicleType(d->xy)) { 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,7 +1157,11 @@ 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(); } 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/rail_cmd.cpp b/src/rail_cmd.cpp index 0429661eda..a44d5d5521 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -1654,8 +1654,9 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile)); /* Update build vehicle window related to this depot */ - InvalidateWindowData(WC_VEHICLE_DEPOT, tile); - InvalidateWindowData(WC_BUILD_VEHICLE, tile); + DepotID depot_id = GetDepotIndex(tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); + InvalidateWindowData(WC_BUILD_VEHICLE, depot_id); } found_convertible_track = true; cost.AddCost(RailConvertCost(type, totype)); @@ -2774,7 +2775,7 @@ static bool ClickTile_Track(TileIndex tile) { if (!IsRailDepot(tile)) return false; - ShowDepotWindow(tile, VEH_TRAIN); + ShowDepotWindow(GetDepotIndex(tile)); return true; } @@ -2973,7 +2974,7 @@ static VehicleEnterTileStatus VehicleEnter_Track(Vehicle *u, TileIndex tile, int if (v->Next() == nullptr) VehicleEnterDepot(v->First()); v->tile = tile; - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); return VETSB_ENTERED_WORMHOLE; } diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index e4489c8621..e387deb372 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -2081,7 +2081,7 @@ static bool ClickTile_Road(TileIndex tile) { if (!IsRoadDepot(tile)) return false; - ShowDepotWindow(tile, VEH_ROAD); + ShowDepotWindow(GetDepotIndex(tile)); return true; } @@ -2269,7 +2269,7 @@ static VehicleEnterTileStatus VehicleEnter_Road(Vehicle *v, TileIndex tile, int, if (rv->Next() == nullptr) VehicleEnterDepot(rv->First()); rv->tile = tile; - InvalidateWindowData(WC_VEHICLE_DEPOT, rv->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(rv->tile)); return VETSB_ENTERED_WORMHOLE; } break; @@ -2543,8 +2543,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/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 0cc513bb05..2ff9258374 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -1043,7 +1043,7 @@ bool RoadVehLeaveDepot(RoadVehicle *v, bool first) v->UpdatePosition(); v->UpdateInclination(true, true); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); return true; } diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index dcd2f6825f..8577169c32 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -424,12 +424,13 @@ static bool CheckShipLeaveDepot(Ship *v) v->cur_speed = 0; v->UpdateViewport(true, true); - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + DepotID depot_id = GetDepotIndex(v->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, depot_id); VehicleServiceInDepot(v); v->LeaveUnbunchingDepot(); v->PlayLeaveStationSound(); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); SetWindowClassesDirty(WC_SHIPS_LIST); return false; diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index f3cefb224e..9bbaf5f4ac 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -2682,7 +2682,6 @@ static CommandCost RemoveAirport(TileIndex tile, DoCommandFlag flags) 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(); @@ -3698,8 +3697,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/train_cmd.cpp b/src/train_cmd.cpp index 9664204f14..a8fa2ea416 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -615,6 +615,8 @@ 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; @@ -645,7 +647,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)); @@ -1363,7 +1365,7 @@ CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID if (dst_head != nullptr) dst_head->First()->MarkDirty(); /* We are undoubtedly changing something in the depot and train list. */ - InvalidateWindowData(WC_VEHICLE_DEPOT, src->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(src->tile)); InvalidateWindowClassesData(WC_TRAINS_LIST, 0); } else { /* We don't want to execute what we're just tried. */ @@ -1447,7 +1449,7 @@ CommandCost CmdSellRailWagon(DoCommandFlag flags, Vehicle *t, bool sell_chain, b NormaliseTrainHead(new_head); /* We are undoubtedly changing something in the depot and train list. */ - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); InvalidateWindowClassesData(WC_TRAINS_LIST, 0); /* Actually delete the sold 'goods' */ @@ -1966,7 +1968,7 @@ void ReverseTrainDirection(Train *v) { if (IsRailDepotTile(v->tile)) { if (IsWholeTrainInsideDepot(v)) return; - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); } /* Clear path reservation in front if train is not stuck. */ @@ -1989,7 +1991,7 @@ void ReverseTrainDirection(Train *v) AdvanceWagonsAfterSwap(v); if (IsRailDepotTile(v->tile)) { - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); } ToggleBit(v->flags, VRF_TOGGLE_REVERSE); @@ -2079,7 +2081,7 @@ CommandCost CmdReverseTrainDirection(DoCommandFlag flags, VehicleID veh_id, bool ToggleBit(v->flags, VRF_REVERSE_DIRECTION); front->ConsistChanged(CCF_ARRANGE); - SetWindowDirty(WC_VEHICLE_DEPOT, front->tile); + if (IsRailDepotTile(front->tile)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(front->tile)); SetWindowDirty(WC_VEHICLE_DETAILS, front->index); SetWindowDirty(WC_VEHICLE_VIEW, front->index); SetWindowClassesDirty(WC_TRAINS_LIST); @@ -2273,7 +2275,7 @@ static bool CheckTrainStayInDepot(Train *v) /* if the train got no power, then keep it in the depot */ if (v->gcache.cached_power == 0) { v->vehstatus |= VS_STOPPED; - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); return true; } @@ -2334,7 +2336,7 @@ static bool CheckTrainStayInDepot(Train *v) v->UpdatePosition(); UpdateSignalsOnSegment(v->tile, INVALID_DIAGDIR, v->owner); v->UpdateAcceleration(); - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); return false; } @@ -3628,7 +3630,7 @@ static void DeleteLastWagon(Train *v) /* Update the depot window if the first vehicle is in depot - * if v == first, then it is updated in PreDestructor() */ if (first->track == TRACK_BIT_DEPOT) { - SetWindowDirty(WC_VEHICLE_DEPOT, first->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(first->tile)); } v->last_station_visited = first->last_station_visited; // for PreDestructor } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 8a7a471e8a..d10f8dcb0f 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -297,7 +297,7 @@ uint Vehicle::Crash(bool) InvalidateWindowClassesData(GetWindowClassForVehicleType(this->type), 0); SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP); SetWindowDirty(WC_VEHICLE_DETAILS, this->index); - SetWindowDirty(WC_VEHICLE_DEPOT, this->tile); + if (IsDepotTile(this->tile)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(this->tile)); delete this->cargo_payment; assert(this->cargo_payment == nullptr); // cleared by ~CargoPayment @@ -868,8 +868,8 @@ void Vehicle::PreDestructor() if (v->disaster_vehicle != INVALID_VEHICLE) ReleaseDisasterVehicle(v->disaster_vehicle); } - if (this->Previous() == nullptr) { - InvalidateWindowData(WC_VEHICLE_DEPOT, this->tile); + if (this->Previous() == nullptr && IsDepotTile(this->tile)) { + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(this->tile)); } if (this->IsPrimaryVehicle()) { @@ -1554,6 +1554,7 @@ void VehicleEnterDepot(Vehicle *v) /* Always work with the front of the vehicle */ assert(v == v->First()); + DepotID depot_id = GetDepotIndex(v->tile); switch (v->type) { case VEH_TRAIN: { Train *t = Train::From(v); @@ -1580,7 +1581,7 @@ void VehicleEnterDepot(Vehicle *v) ship->state = TRACK_BIT_DEPOT; ship->UpdateCache(); ship->UpdateViewport(true, true); - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, depot_id); break; } @@ -1595,9 +1596,9 @@ void VehicleEnterDepot(Vehicle *v) if (v->type != VEH_TRAIN) { /* Trains update the vehicle list when the first unit enters the depot and calls VehicleEnterDepot() when the last unit enters. * We only increase the number of vehicles when the first one enters, so we will not need to search for more vehicles in the depot */ - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); } - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + SetWindowDirty(WC_VEHICLE_DEPOT, depot_id); v->vehstatus |= VS_HIDDEN; v->cur_speed = 0; diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index 6d22d56b0a..ebe5ecc1f5 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -37,6 +37,7 @@ #include "roadveh_cmd.h" #include "train_cmd.h" #include "ship_cmd.h" +#include "depot_base.h" #include #include @@ -178,7 +179,7 @@ std::tuple CmdBuildVehicle(D NormalizeTrainVehInDepot(Train::From(v)); } - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); InvalidateWindowClassesData(GetWindowClassForVehicleType(type), 0); SetWindowDirty(WC_COMPANY, _current_company); if (IsLocalCompany()) { @@ -553,7 +554,7 @@ std::tuple CmdRefitVehicle(DoCommandFla InvalidateWindowData(WC_VEHICLE_DETAILS, front->index); InvalidateWindowClassesData(GetWindowClassForVehicleType(v->type), 0); } - SetWindowDirty(WC_VEHICLE_DEPOT, front->tile); + if (IsDepotTile(front->tile)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(front->tile)); } else { /* Always invalidate the cache; querycost might have filled it. */ v->InvalidateNewGRFCacheOfChain(); @@ -639,7 +640,7 @@ CommandCost CmdStartStopVehicle(DoCommandFlag flags, VehicleID veh_id, bool eval v->MarkDirty(); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); - SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); + if (IsDepotTile(v->tile)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); SetWindowClassesDirty(GetWindowClassForVehicleType(v->type)); InvalidateWindowData(WC_VEHICLE_VIEW, v->index); } diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index 5a0a94d172..07078c00f0 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -1334,7 +1334,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/window_type.h b/src/window_type.h index 0896d5ff6f..6161aeee78 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -346,7 +346,7 @@ enum WindowClass { /** * Depot view; %Window numbers: - * - #TileIndex = #DepotWidgets + * - #DepotID = #DepotWidgets */ WC_VEHICLE_DEPOT, From d643e77800497b883fdc1b1f58c33d0a874b81fd Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Mon, 19 Feb 2024 15:33:03 +0100 Subject: [PATCH 04/78] Change: Refactor some code in build_vehicle_gui. --- src/build_vehicle_gui.cpp | 58 +++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 4d9a2ef647..1a985eb64a 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -1606,6 +1606,35 @@ 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; + 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 + } + } + void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override { switch (widget) { @@ -1668,34 +1697,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; From 742f1de7b4be84ba97c739455b8f849da3cab5c9 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Fri, 25 Aug 2023 14:59:29 +0200 Subject: [PATCH 05/78] Change: Set DepotID related window numbers to WC_BUILD_VEHICLE windows. --- src/build_vehicle_gui.cpp | 50 ++++++++++++++++++++------------------- src/depot_gui.cpp | 4 ++-- src/group_gui.cpp | 2 +- src/vehicle_gui.cpp | 13 +++------- src/vehicle_gui.h | 5 ++-- src/window_type.h | 4 ++-- 6 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 1a985eb64a..abccb01f20 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" @@ -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) ? GetTileOwner(depot->xy) : _local_company; this->eng_list.ForceRebuild(); this->GenerateBuildList(); // generate the list, since we need it in the next line @@ -1264,13 +1263,16 @@ struct BuildVehicleWindow : Window { /** Set the filter type according to the depot type */ void UpdateFilterByTile() { + TileIndex tile = INVALID_TILE; + if (!this->listview_mode) tile = Depot::Get(this->window_number)->xy; + 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.railtype = GetRailType(tile); } break; @@ -1278,9 +1280,9 @@ struct BuildVehicleWindow : Window { if (this->listview_mode) { this->filter.roadtype = INVALID_ROADTYPE; } else { - this->filter.roadtype = GetRoadTypeRoad(this->window_number); + this->filter.roadtype = GetRoadTypeRoad(tile); if (this->filter.roadtype == INVALID_ROADTYPE) { - this->filter.roadtype = GetRoadTypeTram(this->window_number); + this->filter.roadtype = GetRoadTypeTram(tile); } } break; @@ -1326,7 +1328,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; @@ -1613,10 +1615,15 @@ struct BuildVehicleWindow : Window { CargoID cargo = this->cargo_filter_criteria; if (cargo == CargoFilterCriteria::CF_ANY || cargo == CargoFilterCriteria::CF_ENGINES || cargo == CargoFilterCriteria::CF_NONE) cargo = INVALID_CARGO; + + assert(Depot::IsValidID(this->window_number)); + Depot *depot = Depot::Get(this->window_number); + assert(depot->xy != INVALID_TILE); + if (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) { - Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildWagon, this->window_number, sel_eng, true, cargo, INVALID_CLIENT_ID); + Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildWagon, depot->xy, sel_eng, true, cargo, INVALID_CLIENT_ID); } else { - Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildPrimaryVehicle, this->window_number, sel_eng, true, cargo, INVALID_CLIENT_ID); + Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildPrimaryVehicle, depot->xy, sel_eng, true, cargo, INVALID_CLIENT_ID); } /* Update last used variant in hierarchy and refresh if necessary. */ @@ -1930,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/depot_gui.cpp b/src/depot_gui.cpp index c2a6ebce1c..7364840bff 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -769,7 +769,7 @@ struct DepotWindow : Window { case WID_D_BUILD: // Build vehicle ResetObjectToPlace(); - ShowBuildVehicleWindow(Depot::Get(this->window_number)->xy, this->type); + ShowBuildVehicleWindow(this->window_number, this->type); break; case WID_D_CLONE: // Clone button @@ -824,7 +824,7 @@ struct DepotWindow : Window { break; case WID_D_VEHICLE_LIST: - ShowVehicleListWindow(this->owner, this->type, Depot::Get(this->window_number)->xy); + ShowVehicleListWindow(this->owner, this->type, this->window_number); break; case WID_D_AUTOREPLACE: 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/vehicle_gui.cpp b/src/vehicle_gui.cpp index aaaf10f993..bb277bab34 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -2123,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: { @@ -2268,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); } 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/window_type.h b/src/window_type.h index 6161aeee78..a7fa0d34a8 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -383,8 +383,8 @@ enum WindowClass { /** * Build vehicle; %Window numbers: - * - #VehicleType = #BuildVehicleWidgets - * - #TileIndex = #BuildVehicleWidgets + * - #INVALID_DEPOT - VehicleType = #BuildVehicleWidgets + * - #DepotID = #BuildVehicleWidgets */ WC_BUILD_VEHICLE, From 98539782c412834a58eb1cacfac1da1ffcea34df Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Fri, 25 Aug 2023 15:02:16 +0200 Subject: [PATCH 06/78] Change: OrderBackups are indexed through DepotID instead of TileIndex. --- src/depot.cpp | 2 +- src/order_backup.cpp | 37 +++++++++++++++++++++---------------- src/order_backup.h | 9 +++++---- src/order_cmd.h | 2 +- src/saveload/order_sl.cpp | 8 +++++++- src/saveload/saveload.h | 2 ++ src/station_cmd.cpp | 4 ---- 7 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/depot.cpp b/src/depot.cpp index 38330a8739..49297f91fa 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -35,7 +35,7 @@ Depot::~Depot() } /* Clear the order backup. */ - OrderBackup::Reset(this->xy, false); + OrderBackup::Reset(this->index, false); /* Clear the depot from all order-lists */ RemoveOrderFromAllVehicles(OT_GOTO_DEPOT, this->index); diff --git a/src/order_backup.cpp b/src/order_backup.cpp index 9843bbf36f..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; } diff --git a/src/order_backup.h b/src/order_backup.h index 23832d59af..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,9 +54,9 @@ 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); 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/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.h b/src/saveload/saveload.h index 8251fc23ab..3ef079489a 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -387,6 +387,8 @@ enum SaveLoadVersion : uint16_t { 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. + SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 9bbaf5f4ac..a0e8f8c805 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -2679,10 +2679,6 @@ 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); - } st->airport.RemoveHangar(); /* The noise level is the noise from the airport and reduce it to account for the distance to the town center. From cf2a12dfc43c79008922490c9ec8df99c2f4c517 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Wed, 10 May 2023 19:47:55 +0200 Subject: [PATCH 07/78] Change: Move some bits in water tiles for alignment purposes. --- docs/landscape.html | 26 +++++++++++++------------- docs/landscape_grid.html | 8 ++++---- src/saveload/map_sl.cpp | 11 +++++++++++ src/saveload/saveload.h | 2 ++ src/water_map.h | 6 +++--- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/docs/landscape.html b/docs/landscape.html index c48aedcebc..231bfe6bec 100644 --- a/docs/landscape.html +++ b/docs/landscape.html @@ -1047,55 +1047,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)
diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html index 603d78025b..5121d5224f 100644 --- a/docs/landscape_grid.html +++ b/docs/landscape_grid.html @@ -246,7 +246,7 @@ the array so you can quickly see what is used and what is not. OOOO OOOO OOOO OOOO OOOO OOOO OOOO OOOO - OOOO OOOX + OOOO OOOX OOOO OOOO OOOO OOOO OOOO OOOO OOOO OOOO @@ -254,18 +254,18 @@ the array so you can quickly see what is used and what is not. canal, river XXXX XXXX - OOOO OOOO + OOOO OOOO lock OOOO OOOO - OOO1 XX XX + O1OOXX XX shipdepot XXXX XXXX XXXX XXXX OOOO OOOO - 1OOO OOX X + 1OOOOOX X 8 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/saveload.h b/src/saveload/saveload.h index 3ef079489a..1c73661357 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -389,6 +389,8 @@ enum SaveLoadVersion : uint16_t { 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. + SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/water_map.h b/src/water_map.h index 116a37f228..36ac873e63 100644 --- a/src/water_map.h +++ b/src/water_map.h @@ -17,12 +17,12 @@ * 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_COAST_FLAG = 0, ///< Flag for coast. From 3e9b7f19cb4c3e07451f88889d7ca5795f360040 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 27 Mar 2021 13:36:32 +0100 Subject: [PATCH 08/78] Codechange: Add and use GetWaterTileClass. --- src/water_map.h | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/water_map.h b/src/water_map.h index 36ac873e63..18626e8fb4 100644 --- a/src/water_map.h +++ b/src/water_map.h @@ -78,6 +78,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 +97,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 +234,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 +316,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; } /** From a3d9165eb91f4a57d8163512ede5f66fa161abea Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Wed, 10 May 2023 19:52:49 +0200 Subject: [PATCH 09/78] Change: Change rail depot type value in order to align bits. --- docs/landscape.html | 2 +- docs/landscape_grid.html | 2 +- src/rail_map.h | 2 +- src/saveload/afterload.cpp | 9 +++++++++ src/saveload/saveload.h | 1 + 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/landscape.html b/docs/landscape.html index 231bfe6bec..40d73c7fe4 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 diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html index 5121d5224f..6e59a3acac 100644 --- a/docs/landscape_grid.html +++ b/docs/landscape_grid.html @@ -118,7 +118,7 @@ the array so you can quickly see what is used and what is not. XXXX XXXX XXXX XXXX OOOO OOOO OOOO XXXX - 11OX OOXX + 1OOX OOXX 2 diff --git a/src/rail_map.h b/src/rail_map.h index 5ff2fb9ec3..03bca7e6e3 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 }; /** diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 7f35427e0a..3719cbd398 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -641,6 +641,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(); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 1c73661357..dd4d9af27f 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -390,6 +390,7 @@ enum SaveLoadVersion : uint16_t { 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. SL_MAX_VERSION, ///< Highest possible saveload version }; From 76039ae2c18c8cf788f91d69f7c5ff025c97c578 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sun, 27 Dec 2020 21:10:03 +0100 Subject: [PATCH 10/78] Codechange: Use bit alignment for detecting road, rail and water depots. --- src/depot_map.h | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/depot_map.h b/src/depot_map.h index 78a0f0273c..51639a3b41 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,7 +41,11 @@ 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); From c405a59bbc67a374a4796c3befb6c08d69ab1799 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Mon, 8 Apr 2024 18:01:07 +0200 Subject: [PATCH 11/78] Prepare: Add some members to depot struct. --- src/build_vehicle_gui.cpp | 48 +++++++------ src/depot.cpp | 100 ++++++++++++++++++++++++++-- src/depot_base.h | 34 +++++++++- src/depot_cmd.cpp | 5 +- src/depot_gui.cpp | 13 ++-- src/economy.cpp | 7 ++ src/order_cmd.cpp | 23 +------ src/rail.h | 13 ++++ src/rail_cmd.cpp | 2 +- src/road.h | 13 ++++ src/road_cmd.cpp | 2 +- src/saveload/afterload.cpp | 34 ++++++++++ src/saveload/depot_sl.cpp | 6 ++ src/saveload/oldloader_sl.cpp | 2 + src/saveload/saveload.h | 1 + src/script/api/script_depotlist.cpp | 31 ++------- src/station.cpp | 10 ++- src/water_cmd.cpp | 2 +- 18 files changed, 259 insertions(+), 87 deletions(-) diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index abccb01f20..45a93c0c44 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -1168,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. @@ -1248,7 +1248,7 @@ struct BuildVehicleWindow : Window { this->vehicle_editbox.cancel_button = QueryString::ACTION_CLEAR; Depot *depot = Depot::GetIfValid(depot_id); - this->owner = (depot != nullptr) ? GetTileOwner(depot->xy) : _local_company; + 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 @@ -1263,28 +1263,16 @@ struct BuildVehicleWindow : Window { /** Set the filter type according to the depot type */ void UpdateFilterByTile() { - TileIndex tile = INVALID_TILE; - if (!this->listview_mode) tile = Depot::Get(this->window_number)->xy; + 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(tile); - } + 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(tile); - if (this->filter.roadtype == INVALID_ROADTYPE) { - this->filter.roadtype = GetRoadTypeTram(tile); - } - } + this->filter.roadtypes = this->listview_mode ? INVALID_ROADTYPES : depot->r_types.road_types; break; case VEH_SHIP: @@ -1403,7 +1391,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 */ @@ -1463,7 +1451,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; @@ -1503,7 +1491,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, @@ -1743,11 +1731,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); } diff --git a/src/depot.cpp b/src/depot.cpp index 49297f91fa..bc5f62c8bd 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -15,9 +15,12 @@ #include "core/pool_func.hpp" #include "vehicle_gui.h" #include "vehiclelist.h" +#include "command_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,8 +32,9 @@ 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; } @@ -44,6 +48,94 @@ Depot::~Depot() 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()); +} + +/** + * Check we can add some tiles to this depot. + * @param ta The affected tile area. + * @return Whether it is possible to add the tiles or an error message. + */ +CommandCost Depot::BeforeAddTiles(TileArea ta) +{ + assert(ta.tile != INVALID_TILE); + + if (this->ta.tile != INVALID_TILE) { + /* Important when the old rect is completely inside the new rect, resp. the old one was empty. */ + ta.Add(this->ta.tile); + ta.Add(TileAddXY(this->ta.tile, this->ta.w - 1, this->ta.h - 1)); + } + + return CommandCost(); +} + +/** + * Add some tiles to this depot and rescan area for depot_tiles. + * @param ta Affected tile area + * @param adding Whether adding or removing depot tiles. + */ +void Depot::AfterAddRemove(TileArea ta, bool adding) +{ + assert(ta.tile != INVALID_TILE); + + if (adding) { + if (this->ta.tile != INVALID_TILE) { + ta.Add(this->ta.tile); + ta.Add(TileAddXY(this->ta.tile, this->ta.w - 1, this->ta.h - 1)); + } + } else { + ta = this->ta; + } + + this->ta.Clear(); + + for (TileIndex tile : ta) { + if (!IsDepotTile(tile)) continue; + if (GetDepotIndex(tile) != this->index) continue; + this->ta.Add(tile); + } + + if (this->ta.tile != INVALID_TILE) { + this->RescanDepotTiles(); + assert(!this->depot_tiles.empty()); + this->xy = this->depot_tiles[0]; + } else { + delete this; + } +} + +/** + * Rescan depot_tiles. Done after AfterAddRemove and SaveLoad. + * Updates the tiles of the depot and its railtypes/roadtypes... + */ +void Depot::RescanDepotTiles() +{ + this->depot_tiles.clear(); + RailTypes old_rail_types = this->r_types.rail_types; + this->r_types.rail_types = RAILTYPES_NONE; + + for (TileIndex tile : this->ta) { + if (!IsDepotTile(tile)) continue; + if (GetDepotIndex(tile) != this->index) continue; + this->depot_tiles.push_back(tile); + switch (veh_type) { + case VEH_ROAD: + this->r_types.road_types |= GetPresentRoadTypes(tile); + break; + case VEH_TRAIN: + this->r_types.rail_types |= (RailTypes)(1 << GetRailType(tile)); + break; + case VEH_SHIP: + /* Mark this ship depot has at least one part, so ships can be built. */ + this->r_types.rail_types |= INVALID_RAILTYPES; + break; + default: break; + } + } + + if (old_rail_types != this->r_types.rail_types) { + InvalidateWindowData(WC_BUILD_VEHICLE, this->index, 0, true); + } } diff --git a/src/depot_base.h b/src/depot_base.h index 1d8330fc74..66c6dc3472 100644 --- a/src/depot_base.h +++ b/src/depot_base.h @@ -13,10 +13,14 @@ #include "depot_map.h" #include "core/pool_type.hpp" #include "timer/timer_game_calendar.h" +#include "rail_type.h" +#include "road_type.h" typedef Pool DepotPool; extern DepotPool _depot_pool; +class CommandCost; + 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,11 +29,30 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> { std::string name; TimerGameCalendar::Date build_date; ///< Date of construction - Depot(TileIndex xy = INVALID_TILE) : xy(xy) {} + VehicleType veh_type; ///< Vehicle type of the depot. + Owner owner; ///< Owner of the depot. + Station *station; ///< For aircraft, station associated with this hangar. + + union { + RoadTypes road_types; + RailTypes rail_types; + } r_types; + + TileArea ta; + std::vector depot_tiles; + + Depot(TileIndex xy = INVALID_TILE, VehicleType type = VEH_INVALID, Owner owner = INVALID_OWNER, Station *station = nullptr) : + xy(xy), + veh_type(type), + owner(owner), + station(station), + ta(xy, 1, 1) {} + ~Depot(); static inline Depot *GetByTile(TileIndex tile) { + assert(Depot::IsValidID(GetDepotIndex(tile))); return Depot::Get(GetDepotIndex(tile)); } @@ -43,6 +66,15 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> { { return GetTileType(d->xy) == GetTileType(this->xy); } + + /* Check we can add some tiles to this depot. */ + CommandCost BeforeAddTiles(TileArea ta); + + /* Add some tiles to this depot and rescan area for depot_tiles. */ + void AfterAddRemove(TileArea ta, bool adding); + + /* Rescan depot_tiles. Done after AfterAddRemove and SaveLoad. */ + void RescanDepotTiles(); }; #endif /* DEPOT_BASE_H */ diff --git a/src/depot_cmd.cpp b/src/depot_cmd.cpp index 1e1700f6cb..5b29591616 100644 --- a/src/depot_cmd.cpp +++ b/src/depot_cmd.cpp @@ -48,7 +48,7 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str Depot *d = Depot::GetIfValid(depot_id); if (d == nullptr) return CMD_ERROR; - CommandCost ret = CheckTileOwnership(d->xy); + CommandCost ret = CheckOwnership(d->owner); if (ret.Failed()) return ret; bool reset = text.empty(); @@ -71,8 +71,7 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str 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(); } diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp index 7364840bff..7c27f92c96 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -29,6 +29,7 @@ #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" @@ -270,13 +271,13 @@ struct DepotWindow : Window { { assert(Depot::IsValidID(depot_id)); Depot *depot = Depot::Get(depot_id); - assert(IsCompanyBuildableVehicleType(GetDepotVehicleType(depot->xy))); + assert(IsCompanyBuildableVehicleType(depot->veh_type)); this->sel = INVALID_VEHICLE; this->vehicle_over = INVALID_VEHICLE; this->generate_list = true; this->hovered_widget = -1; - this->type = GetDepotVehicleType(depot->xy); + this->type = depot->veh_type; this->num_columns = 1; // for non-trains this gets set in FinishInitNested() this->unitnumber_digits = 2; @@ -292,7 +293,7 @@ struct DepotWindow : Window { this->SetupWidgetData(this->type); this->FinishInitNested(depot_id); - this->owner = GetTileOwner(depot->xy); + this->owner = depot->owner; OrderBackup::Reset(); } @@ -428,7 +429,7 @@ struct DepotWindow : Window { if (widget != WID_D_CAPTION) return; SetDParam(0, this->type); - SetDParam(1, (this->type == VEH_AIRCRAFT) ? GetStationIndex(Depot::Get(this->window_number)->xy) : this->window_number); + SetDParam(1, (this->type == VEH_AIRCRAFT) ? Depot::Get(this->window_number)->station->index : this->window_number); } struct GetDepotVehiclePtData { @@ -1121,7 +1122,7 @@ static void DepotSellAllConfirmationCallback(Window *win, bool confirmed) if (confirmed) { assert(Depot::IsValidID(win->window_number)); Depot *d = Depot::Get(win->window_number); - Command::Post(d->xy, GetDepotVehicleType(d->xy)); + Command::Post(d->xy, d->veh_type); } } @@ -1135,7 +1136,7 @@ void ShowDepotWindow(DepotID depot_id) if (BringWindowToFrontById(WC_VEHICLE_DEPOT, depot_id) != nullptr) return; Depot *d = Depot::Get(depot_id); - switch (GetDepotVehicleType(d->xy)) { + switch (d->veh_type) { default: NOT_REACHED(); case VEH_TRAIN: new DepotWindow(_train_depot_desc, depot_id); break; case VEH_ROAD: new DepotWindow(_road_depot_desc, depot_id); break; diff --git a/src/economy.cpp b/src/economy.cpp index cd99094239..5c0d4f7bec 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -55,6 +55,7 @@ #include "timer/timer.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" +#include "depot_base.h" #include "table/strings.h" #include "table/pricebase.h" @@ -374,6 +375,12 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner) } if (new_owner == INVALID_OWNER) RebuildSubsidisedSourceAndDestinationCache(); + for (Depot *dep : Depot::Iterate()) { + if (dep->owner == old_owner && new_owner != INVALID_OWNER) { + dep->owner = new_owner; + } + } + /* Take care of rating and transport rights in towns */ for (Town *t : Town::Iterate()) { /* If a company takes over, give the ratings to that company. */ diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 070b0d84b1..9252e3d516 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -789,28 +789,11 @@ CommandCost CmdInsertOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID se if (dp == nullptr) return CMD_ERROR; - ret = CheckOwnership(GetTileOwner(dp->xy)); + ret = CheckOwnership(dp->owner); 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; - - case VEH_AIRCRAFT: - if (!CanVehicleUseStation(v, Station::GetByTile(dp->xy)) || !IsHangarTile(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; 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 a44d5d5521..91dff791db 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -1010,7 +1010,7 @@ CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType rai if (rotate_existing_depot) { SetRailDepotExitDirection(tile, dir); } else { - Depot *d = new Depot(tile); + Depot *d = new Depot(tile, VEH_TRAIN, _current_company); d->build_date = TimerGameCalendar::date; MakeRailDepot(tile, _current_company, d->index, dir, railtype); 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 e387deb372..7760a90fbe 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -1188,7 +1188,7 @@ CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, if (rotate_existing_depot) { SetRoadDepotExitDirection(tile, dir); } else { - Depot *dep = new Depot(tile); + Depot *dep = new Depot(tile, VEH_ROAD, _current_company); dep->build_date = TimerGameCalendar::date; MakeRoadDepot(tile, _current_company, dep->index, dir, rt); MakeDefaultName(dep); diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 3719cbd398..4c13d6985f 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -294,6 +294,10 @@ static void InitializeWindowsAndCaches() } } + for (Depot *dep : Depot::Iterate()) { + dep->RescanDepotTiles(); + } + RecomputePrices(); GroupStatistics::UpdateAfterLoad(); @@ -2823,6 +2827,36 @@ bool AfterLoadGame() } } + if (IsSavegameVersionBefore(SLV_ADD_MEMBERS_TO_DEPOT_STRUCT)) { + for (Depot *depot : Depot::Iterate()) { + if (!IsDepotTile(depot->xy) || GetDepotIndex(depot->xy) != depot->index) { + /* It can happen there is no depot here anymore (TTO/TTD savegames) */ + depot->veh_type = VEH_INVALID; + depot->owner = INVALID_OWNER; + delete depot; + continue; + } + + depot->owner = GetTileOwner(depot->xy); + depot->veh_type = GetDepotVehicleType(depot->xy); + switch (depot->veh_type) { + case VEH_SHIP: + depot->AfterAddRemove(TileArea(depot->xy, 2, 2), true); + break; + case VEH_ROAD: + case VEH_TRAIN: + depot->AfterAddRemove(TileArea(depot->xy, 1, 1), true); + break; + case VEH_AIRCRAFT: + assert(IsHangarTile(depot->xy)); + depot->station = Station::GetByTile(depot->xy); + break; + default: + break; + } + } + } + /* In old versions it was possible to remove an airport while a plane was * taking off or landing. This gives all kind of problems when building * another airport in the same station so we don't allow that anymore. diff --git a/src/saveload/depot_sl.cpp b/src/saveload/depot_sl.cpp index 2a7859211c..1987605ea3 100644 --- a/src/saveload/depot_sl.cpp +++ b/src/saveload/depot_sl.cpp @@ -27,6 +27,12 @@ static const SaveLoad _depot_desc[] = { SLE_CONDVAR(Depot, town_cn, SLE_UINT16, SLV_141, SL_MAX_VERSION), SLE_CONDSSTR(Depot, name, SLE_STR, SLV_141, SL_MAX_VERSION), SLE_CONDVAR(Depot, build_date, SLE_INT32, SLV_142, SL_MAX_VERSION), + SLE_CONDVAR(Depot, owner, SLE_UINT8, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDVAR(Depot, veh_type, SLE_UINT8, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDVAR(Depot, ta.tile, SLE_UINT32, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDVAR(Depot, ta.w, SLE_FILE_U8 | SLE_VAR_U16, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDVAR(Depot, ta.h, SLE_FILE_U8 | SLE_VAR_U16, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), + SLE_CONDREF(Depot, station, REF_STATION, SLV_ADD_MEMBERS_TO_DEPOT_STRUCT, SL_MAX_VERSION), }; struct DEPTChunkHandler : ChunkHandler { diff --git a/src/saveload/oldloader_sl.cpp b/src/saveload/oldloader_sl.cpp index 538c1336c3..dc48d09399 100644 --- a/src/saveload/oldloader_sl.cpp +++ b/src/saveload/oldloader_sl.cpp @@ -690,6 +690,8 @@ static bool LoadOldDepot(LoadgameState *ls, int num) if (d->xy != 0) { d->town = RemapTown(d->xy); } else { + d->owner = INVALID_OWNER; + d->veh_type = VEH_INVALID; delete d; } diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index dd4d9af27f..6e4b830836 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -391,6 +391,7 @@ enum SaveLoadVersion : uint16_t { 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. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/script/api/script_depotlist.cpp b/src/script/api/script_depotlist.cpp index eb43139165..7997d399e7 100644 --- a/src/script/api/script_depotlist.cpp +++ b/src/script/api/script_depotlist.cpp @@ -17,33 +17,16 @@ ScriptDepotList::ScriptDepotList(ScriptTile::TransportType transport_type) { EnforceDeityOrCompanyModeValid_Void(); - ::TileType tile_type; - switch (transport_type) { - default: return; + static_assert(VEH_TRAIN == (int)ScriptTile::TRANSPORT_RAIL); + static_assert(VEH_ROAD == (int)ScriptTile::TRANSPORT_ROAD); + static_assert(VEH_SHIP == (int)ScriptTile::TRANSPORT_WATER); - case ScriptTile::TRANSPORT_ROAD: tile_type = ::MP_ROAD; break; - case ScriptTile::TRANSPORT_RAIL: tile_type = ::MP_RAILWAY; break; - case ScriptTile::TRANSPORT_WATER: tile_type = ::MP_WATER; break; - - case ScriptTile::TRANSPORT_AIR: { - /* Hangars are not seen as real depots by the depot code. */ - bool is_deity = ScriptCompanyMode::IsDeity(); - CompanyID owner = ScriptObject::GetCompany(); - for (const Station *st : Station::Iterate()) { - if (is_deity || st->owner == owner) { - for (uint i = 0; i < st->airport.GetNumHangars(); i++) { - this->AddItem(st->airport.GetHangarTile(i).base()); - } - } - } - return; - } - } - - /* Handle 'standard' depots. */ bool is_deity = ScriptCompanyMode::IsDeity(); CompanyID owner = ScriptObject::GetCompany(); for (const Depot *depot : Depot::Iterate()) { - if ((is_deity || ::GetTileOwner(depot->xy) == owner) && ::IsTileType(depot->xy, tile_type)) this->AddItem(depot->xy.base()); + if (depot->veh_type != (VehicleType)transport_type || + (!is_deity && ::GetTileOwner(depot->xy) != owner)) continue; + + this->AddItem(depot->xy.base()); } } diff --git a/src/station.cpp b/src/station.cpp index 0ee8f2a605..0d4198ac6c 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -737,9 +737,17 @@ void Airport::AddHangar() assert(Depot::CanAllocateItem()); assert(this->GetNumHangars() > 0); Station *st = Station::GetByTile(this->GetHangarTile(0)); - this->hangar = new Depot(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)); + } } /** diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index 07078c00f0..54f943451b 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -134,7 +134,7 @@ CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis) } if (flags & DC_EXEC) { - Depot *depot = new Depot(tile); + Depot *depot = new Depot(tile, VEH_SHIP, _current_company); depot->build_date = TimerGameCalendar::date; uint new_water_infra = 2 * LOCK_DEPOT_TILE_FACTOR; From cec541ac2c856f7a6859bc4934a60ed0919aa485 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 22 Apr 2023 13:01:37 +0200 Subject: [PATCH 12/78] Change: BuildDepotVehicleList through a DepotId instead of a TileIndex. --- src/depot_gui.cpp | 2 +- src/vehicle_cmd.cpp | 6 +++--- src/vehiclelist.cpp | 18 ++++++++++++------ src/vehiclelist.h | 3 ++- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp index 7c27f92c96..231fe63577 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -714,7 +714,7 @@ struct DepotWindow : Window { 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, Depot::Get(this->window_number)->xy, &this->vehicle_list, &this->wagon_list); + BuildDepotVehicleList(this->type, this->window_number, &this->vehicle_list, &this->wagon_list); this->generate_list = false; DepotSortList(&this->vehicle_list); diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index ebe5ecc1f5..16ec099551 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -668,7 +668,7 @@ CommandCost CmdMassStartStopVehicle(DoCommandFlag flags, TileIndex tile, bool do } else { if (!IsDepotTile(tile) || !IsTileOwner(tile, _current_company)) return CMD_ERROR; /* Get the list of vehicles in the depot */ - BuildDepotVehicleList(vli.vtype, tile, &list, nullptr); + BuildDepotVehicleList(vli.vtype, GetDepotIndex(tile), &list, nullptr); } for (const Vehicle *v : list) { @@ -700,7 +700,7 @@ CommandCost CmdDepotSellAllVehicles(DoCommandFlag flags, TileIndex tile, Vehicle if (!IsDepotTile(tile) || !IsTileOwner(tile, _current_company)) return CMD_ERROR; /* Get the list of vehicles in the depot */ - BuildDepotVehicleList(vehicle_type, tile, &list, &list); + BuildDepotVehicleList(vehicle_type, GetDepotIndex(tile), &list, &list); CommandCost last_error = CMD_ERROR; bool had_success = false; @@ -733,7 +733,7 @@ CommandCost CmdDepotMassAutoReplace(DoCommandFlag flags, TileIndex tile, Vehicle if (!IsDepotTile(tile) || !IsTileOwner(tile, _current_company)) return CMD_ERROR; /* Get the list of vehicles in the depot */ - BuildDepotVehicleList(vehicle_type, tile, &list, &list, true); + BuildDepotVehicleList(vehicle_type, GetDepotIndex(tile), &list, &list, true); for (const Vehicle *v : list) { /* Ensure that the vehicle completely in the depot */ diff --git a/src/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 */ From c6e69c03c9674d841c8b35e6b3252b733d438f41 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Fri, 25 Aug 2023 16:41:55 +0200 Subject: [PATCH 13/78] Add: Settings for controlling depot spread. --- src/depot.cpp | 3 +++ src/lang/english.txt | 7 +++++++ src/saveload/afterload.cpp | 6 ++++++ src/saveload/saveload.h | 1 + src/settings_gui.cpp | 6 ++++++ src/settings_type.h | 8 ++++++++ src/table/settings/game_settings.ini | 25 +++++++++++++++++++++++++ 7 files changed, 56 insertions(+) diff --git a/src/depot.cpp b/src/depot.cpp index bc5f62c8bd..dfcdc5c1fd 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -68,6 +68,9 @@ CommandCost Depot::BeforeAddTiles(TileArea ta) ta.Add(TileAddXY(this->ta.tile, this->ta.w - 1, this->ta.h - 1)); } + if ((ta.w > _settings_game.depot.depot_spread) || (ta.h > _settings_game.depot.depot_spread)) { + return_cmd_error(STR_ERROR_DEPOT_TOO_SPREAD_OUT); + } return CommandCost(); } diff --git a/src/lang/english.txt b/src/lang/english.txt index 87a8d3c594..244437da1f 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1615,6 +1615,11 @@ 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_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 +2142,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 @@ -5132,6 +5138,7 @@ STR_ERROR_REAR_ENGINE_FOLLOW_FRONT :{WHITE}The rear 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_DEPOT_TOO_SPREAD_OUT :{WHITE}... depot too spread out STR_ERROR_DEPOT_WRONG_DEPOT_TYPE :Wrong depot type # Depot unbunching related errors diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 4c13d6985f..42e605ffaa 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -808,6 +808,12 @@ 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; + } + /* Load the sprites */ GfxLoadSprites(); LoadStringWidthTable(); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 6e4b830836..9affcf9e3d 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -392,6 +392,7 @@ enum SaveLoadVersion : uint16_t { 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. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index fb5c08ec86..1b3fd4a8fe 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -2145,6 +2145,12 @@ 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")); + } + 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_type.h b/src/settings_type.h index ae6f22c42e..22b1a44bdd 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -570,6 +570,13 @@ struct StationSettings { uint8_t station_spread; ///< amount a station may spread }; +/** Settings related to depots. */ +struct DepotSettings { + uint8_t depot_spread; ///< amount a depot may spread + bool adjacent_depots; ///< allow depots to be built directly adjacent to other depots + bool distant_join_depots; ///< allow to join non-adjacent depots +}; + /** Default settings for vehicles. */ struct VehicleDefaultSettings { bool servint_ispercent; ///< service intervals are in percents @@ -603,6 +610,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/table/settings/game_settings.ini b/src/table/settings/game_settings.ini index 07adda5cc9..0ad82ace3e 100644 --- a/src/table/settings/game_settings.ini +++ b/src/table/settings/game_settings.ini @@ -145,6 +145,31 @@ str = STR_CONFIG_SETTING_DISTANT_JOIN_STATIONS strhelp = STR_CONFIG_SETTING_DISTANT_JOIN_STATIONS_HELPTEXT post_cb = [](auto) { CloseWindowById(WC_SELECT_STATION, 0); } +[SDT_BOOL] +var = depot.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 + +[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 +cat = SC_BASIC + [SDT_OMANY] var = vehicle.road_side type = SLE_UINT8 From 791124f0583c00402826fe3d378f68ae9820e20e Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Fri, 25 Aug 2023 16:42:58 +0200 Subject: [PATCH 14/78] Feature: Add a window for joining depots. --- src/depot.cpp | 3 + src/depot_cmd.cpp | 66 ++++++++ src/depot_func.h | 8 + src/depot_gui.cpp | 242 +++++++++++++++++++++++++++ src/depot_type.h | 1 + src/dock_gui.cpp | 8 + src/lang/english.txt | 6 + src/rail_gui.cpp | 8 + src/road_gui.cpp | 8 + src/table/settings/game_settings.ini | 2 + src/widgets/depot_widget.h | 7 + src/window_type.h | 6 + 12 files changed, 365 insertions(+) diff --git a/src/depot.cpp b/src/depot.cpp index dfcdc5c1fd..283cc0b271 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -100,6 +100,7 @@ void Depot::AfterAddRemove(TileArea ta, bool adding) this->ta.Add(tile); } + VehicleType veh_type = this->veh_type; if (this->ta.tile != INVALID_TILE) { this->RescanDepotTiles(); assert(!this->depot_tiles.empty()); @@ -107,6 +108,8 @@ void Depot::AfterAddRemove(TileArea ta, bool adding) } else { delete this; } + + InvalidateWindowData(WC_SELECT_DEPOT, veh_type); } /** diff --git a/src/depot_cmd.cpp b/src/depot_cmd.cpp index 5b29591616..32cd01558b 100644 --- a/src/depot_cmd.cpp +++ b/src/depot_cmd.cpp @@ -75,3 +75,69 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str } return CommandCost(); } + +/** + * Look for or check depot to join to, building a new one if necessary. + * @param ta The area of the new depot. + * @param veh_type The vehicle type of the new depot. + * @param join_to DepotID of the depot to join to. + * If INVALID_DEPOT, look whether it is possible to join to an existing depot. + * If NEW_DEPOT, directly create a new depot. + * @param depot The pointer to the depot. + * @param adjacent Whether adjacent depots are allowed + * @return command cost with the error or 'okay' + */ +CommandCost FindJoiningDepot(TileArea ta, VehicleType veh_type, DepotID &join_to, Depot *&depot, bool adjacent, DoCommandFlag flags) +{ + /* Look for a joining depot if needed. */ + if (join_to == INVALID_DEPOT) { + assert(depot == nullptr); + DepotID closest_depot = INVALID_DEPOT; + + TileArea check_area(ta); + check_area.Expand(1); + + /* Check around to see if there's any depot there. */ + for (TileIndex tile_cur : check_area) { + if (IsValidTile(tile_cur) && IsDepotTile(tile_cur)) { + Depot *d = Depot::GetByTile(tile_cur); + assert(d != nullptr); + if (d->veh_type != veh_type) continue; + if (d->owner != _current_company) continue; + + if (closest_depot == INVALID_DEPOT) { + closest_depot = d->index; + } else if (closest_depot != d->index) { + if (!adjacent) return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING_DEPOT); + } + } + } + + if (closest_depot != INVALID_DEPOT) { + assert(Depot::IsValidID(closest_depot)); + depot = Depot::Get(closest_depot); + } + + join_to = depot == nullptr ? NEW_DEPOT : depot->index; + } + + /* At this point, join_to is NEW_DEPOT or a valid DepotID. */ + + if (join_to == NEW_DEPOT) { + /* New depot needed. */ + if (!Depot::CanAllocateItem()) return CMD_ERROR; + if (flags & DC_EXEC) { + depot = new Depot(ta.tile, veh_type, _current_company); + depot->build_date = TimerGameCalendar::date; + } + } else { + /* Joining depots. */ + assert(Depot::IsValidID(join_to)); + depot = Depot::Get(join_to); + assert(depot->owner == _current_company); + assert(depot->veh_type == veh_type); + return depot->BeforeAddTiles(ta); + } + + return CommandCost(); +} diff --git a/src/depot_func.h b/src/depot_func.h index 7b3b6a9eeb..d4d5ab69aa 100644 --- a/src/depot_func.h +++ b/src/depot_func.h @@ -10,8 +10,10 @@ #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(DepotID depot_id); void InitDepotWindowBlockSizes(); @@ -33,4 +35,10 @@ 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); + #endif /* DEPOT_FUNC_H */ diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp index 231fe63577..bd85a59458 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -33,6 +33,7 @@ #include "train_cmd.h" #include "vehicle_cmd.h" #include "core/geometry_func.hpp" +#include "depot_func.h" #include "widgets/depot_widget.h" @@ -1167,3 +1168,244 @@ void DeleteDepotHighlightOfVehicle(const Vehicle *v) if (w->sel == v->index) ResetObjectToPlace(); } } + +static std::vector _depots_nearby_list; + +/** Structure with user-data for AddNearbyDepot. */ +struct AddNearbyDepotData { + TileArea search_area; ///< Search area. + VehicleType type; ///< Vehicle type of the searched depots. +}; + +/** + * Add depot on this tile to _depots_nearby_list if it's fully within the + * depot spread. + * @param tile Tile just being checked + * @param user_data Pointer to TileArea context + */ +static bool AddNearbyDepot(TileIndex tile, void *user_data) +{ + AddNearbyDepotData *andd = (AddNearbyDepotData *)user_data; + + /* Check if own depot and if we stay within station spread */ + if (!IsDepotTile(tile)) return false; + Depot *dep = Depot::GetByTile(tile); + if (dep->owner != _local_company || dep->veh_type != andd->type || + (find(_depots_nearby_list.begin(), _depots_nearby_list.end(), dep->index) != _depots_nearby_list.end())) { + return false; + } + + CommandCost cost = dep->BeforeAddTiles(andd->search_area); + if (cost.Succeeded()) { + _depots_nearby_list.push_back(dep->index); + } + + return false; +} + +/** + * Circulate around the to-be-built depot to find depots we could join. + * Make sure that only depots are returned where joining wouldn't exceed + * depot spread and are our own depot. + * @param ta Base tile area of the to-be-built depot + * @param veh_type Vehicle type depots to look for + * @param distant_join Search for adjacent depots (false) or depots fully + * within depot spread + */ +static const Depot *FindDepotsNearby(TileArea ta, VehicleType veh_type, bool distant_join) +{ + _depots_nearby_list.clear(); + _depots_nearby_list.push_back(NEW_DEPOT); + + /* Check the inside, to return, if we sit on another big depot */ + Depot *depot; + for (TileIndex t : ta) { + if (!IsDepotTile(t)) continue; + depot = Depot::GetByTile(t); + if (depot->veh_type == veh_type && depot->owner == _current_company) return depot; + } + + /* Only search tiles where we have a chance to stay within the depot spread. + * The complete check needs to be done in the callback as we don't know the + * extent of the found depot, yet. */ + if (distant_join && std::min(ta.w, ta.h) >= _settings_game.depot.depot_spread) return nullptr; + uint max_dist = distant_join ? _settings_game.depot.depot_spread - std::min(ta.w, ta.h) : 1; + + AddNearbyDepotData andd; + andd.search_area = ta; + andd.type = veh_type; + + TileIndex tile = TileAddByDir(andd.search_area.tile, DIR_N); + CircularTileSearch(&tile, max_dist, ta.w, ta.h, AddNearbyDepot, &andd); + + return nullptr; +} + +/** + * Check whether we need to show the depot selection window. + * @param ta Tile area of the to-be-built depot. + * @param proc The procedure for the depot picker. + * @param veh_type the vehicle type of the depot. + * @return whether we need to show the depot selection window. + */ +static bool DepotJoinerNeeded(TileArea ta, VehicleType veh_type) +{ + /* If a window is already opened and we didn't ctrl-click, + * return true (i.e. just flash the old window) */ + Window *selection_window = FindWindowById(WC_SELECT_DEPOT, veh_type); + if (selection_window != nullptr) { + /* Abort current distant-join and start new one */ + selection_window->Close(); + UpdateTileSelection(); + } + + /* Only show the popup if we press ctrl. */ + if (!_ctrl_pressed) return false; + + /* Test for adjacent depot or depot below selection. + * If adjacent-stations is disabled and we are building next to a depot, do not show the selection window. + * but join the other depot immediately. */ + 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); + } + + 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(); + } +}; + +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); + } +} diff --git a/src/depot_type.h b/src/depot_type.h index 4e61c1bcbd..2fdafc00ea 100644 --- a/src/depot_type.h +++ b/src/depot_type.h @@ -14,6 +14,7 @@ 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' diff --git a/src/dock_gui.cpp b/src/dock_gui.cpp index ffbf5aa63e..ccf35c3e44 100644 --- a/src/dock_gui.cpp +++ b/src/dock_gui.cpp @@ -275,6 +275,7 @@ struct BuildDocksToolbarWindow : Window { 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); } @@ -529,6 +530,12 @@ public: UpdateDocksDirection(); } + void Close([[maybe_unused]] int data = 0) override + { + CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP); + 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 +576,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); diff --git a/src/lang/english.txt b/src/lang/english.txt index 244437da1f..34c296c15f 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2769,6 +2769,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 @@ -5120,6 +5125,7 @@ STR_ERROR_CAN_T_BUILD_ROAD_DEPOT :{WHITE}Can't bu STR_ERROR_CAN_T_BUILD_TRAM_DEPOT :{WHITE}Can't build tram vehicle depot here... STR_ERROR_CAN_T_BUILD_SHIP_DEPOT :{WHITE}Can't build ship depot here... +STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING_DEPOT :{WHITE}... adjoins more than one existing depot STR_ERROR_CAN_T_RENAME_DEPOT :{WHITE}Can't rename depot... STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT :{WHITE}... must be stopped inside a depot diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 06819e4c3d..016ad51c59 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -804,6 +804,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); } @@ -1705,6 +1706,12 @@ struct BuildRailDepotWindow : public PickerWindowBase { this->LowerWidget(WID_BRAD_DEPOT_NE + _build_depot_direction); } + void Close([[maybe_unused]] int data = 0) override + { + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); + this->PickerWindowBase::Close(); + } + void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override { if (!IsInsideMM(widget, WID_BRAD_DEPOT_NE, WID_BRAD_DEPOT_NW + 1)) return; @@ -1734,6 +1741,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); diff --git a/src/road_gui.cpp b/src/road_gui.cpp index 27066a2144..3f9b9251cf 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -687,6 +687,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); } @@ -1106,6 +1107,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 +1142,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); diff --git a/src/table/settings/game_settings.ini b/src/table/settings/game_settings.ini index 0ad82ace3e..f2b8f2d569 100644 --- a/src/table/settings/game_settings.ini +++ b/src/table/settings/game_settings.ini @@ -157,6 +157,7 @@ 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 @@ -168,6 +169,7 @@ 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_OMANY] diff --git a/src/widgets/depot_widget.h b/src/widgets/depot_widget.h index f94f1263d2..5c98203a03 100644 --- a/src/widgets/depot_widget.h +++ b/src/widgets/depot_widget.h @@ -32,4 +32,11 @@ enum DepotWidgets : WidgetID { WID_D_START_ALL, ///< Start all button. }; +/** Widgets of the #SelectDepotWindow class. */ +enum JoinDepotWidgets { + WID_JD_CAPTION, // Caption of the window. + WID_JD_PANEL, // Main panel. + WID_JD_SCROLLBAR, // Scrollbar of the panel. +}; + #endif /* WIDGETS_DEPOT_WIDGET_H */ diff --git a/src/window_type.h b/src/window_type.h index a7fa0d34a8..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 From c46660d929b073afd6cb54b26af4785076b6f9f9 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Fri, 25 Aug 2023 16:43:49 +0200 Subject: [PATCH 15/78] Feature: Highlight tiles of a depot and adjacent depot tiles when building. --- src/depot_func.h | 3 ++ src/depot_gui.cpp | 105 +++++++++++++++++++++++++++++++++++++ src/dock_gui.cpp | 10 ++++ src/lang/english.txt | 2 + src/rail_gui.cpp | 9 ++++ src/road_gui.cpp | 9 ++++ src/viewport.cpp | 38 ++++++++++++++ src/viewport_func.h | 3 ++ src/widgets/depot_widget.h | 1 + 9 files changed, 180 insertions(+) diff --git a/src/depot_func.h b/src/depot_func.h index d4d5ab69aa..f214e6d848 100644 --- a/src/depot_func.h +++ b/src/depot_func.h @@ -41,4 +41,7 @@ CommandCost FindJoiningDepot(TileArea ta, VehicleType veh_type, DepotID &join_to 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 bd85a59458..511099bcd1 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -80,6 +80,7 @@ static constexpr NWidgetPart _nested_train_depot_widgets[] = { NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_D_BUILD), SetDataTip(0x0, STR_NULL), SetFill(1, 1), SetResize(1, 0), NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_D_CLONE), SetDataTip(0x0, STR_NULL), SetFill(1, 1), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_D_HIGHLIGHT), SetDataTip(STR_BUTTON_HIGHLIGHT_DEPOT, STR_TOOLTIP_HIGHLIGHT_DEPOT), SetFill(1, 1), SetResize(1, 0), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_D_VEHICLE_LIST), SetDataTip(0x0, STR_NULL), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetFill(0, 1), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_D_STOP_ALL), SetDataTip(SPR_FLAG_VEH_STOPPED, STR_NULL), SetAspect(WidgetDimensions::ASPECT_VEHICLE_FLAG), SetFill(0, 1), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_D_START_ALL), SetDataTip(SPR_FLAG_VEH_RUNNING, STR_NULL), SetAspect(WidgetDimensions::ASPECT_VEHICLE_FLAG), SetFill(0, 1), @@ -303,6 +304,7 @@ struct DepotWindow : Window { CloseWindowById(WC_BUILD_VEHICLE, this->window_number); 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(); } @@ -712,6 +714,9 @@ struct DepotWindow : Window { void OnPaint() override { + extern DepotID _viewport_highlight_depot; + this->SetWidgetLoweredState(WID_D_HIGHLIGHT, _viewport_highlight_depot == this->window_number); + if (this->generate_list) { /* Generate the vehicle list * It's ok to use the wagon pointers for non-trains as they will be ignored */ @@ -804,6 +809,11 @@ struct DepotWindow : Window { 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); @@ -1286,6 +1296,15 @@ struct SelectDepotWindow : Window { 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 @@ -1370,6 +1389,19 @@ struct SelectDepotWindow : Window { 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[] = { @@ -1409,3 +1441,76 @@ void ShowSelectDepotIfNeeded(TileArea ta, DepotPickerCmdProc proc, VehicleType v 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/dock_gui.cpp b/src/dock_gui.cpp index ccf35c3e44..7ba951d141 100644 --- a/src/dock_gui.cpp +++ b/src/dock_gui.cpp @@ -32,6 +32,7 @@ #include "waypoint_cmd.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "depot_func.h" #include "widgets/dock_widget.h" @@ -112,6 +113,8 @@ struct BuildDocksToolbarWindow : Window { { if (_game_mode == GM_NORMAL && this->IsWidgetLowered(WID_DT_STATION)) SetViewportCatchmentStation(nullptr, true); if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false); + if (_game_mode == GM_NORMAL && this->IsWidgetLowered(WID_DT_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); + this->Window::Close(); } @@ -270,6 +273,8 @@ struct BuildDocksToolbarWindow : Window { { if (_game_mode != GM_EDITOR && this->IsWidgetLowered(WID_DT_STATION)) SetViewportCatchmentStation(nullptr, true); + if (_game_mode != GM_EDITOR && this->IsWidgetLowered(WID_DT_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); + this->RaiseButtons(); CloseWindowById(WC_BUILD_STATION, TRANSPORT_WATER); @@ -586,6 +591,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/lang/english.txt b/src/lang/english.txt index 34c296c15f..a07296d36e 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 diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 016ad51c59..dcb0780919 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" @@ -454,6 +455,7 @@ struct BuildRailToolbarWindow : Window { if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) SetViewportCatchmentWaypoint(nullptr, true); if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false); CloseWindowById(WC_SELECT_STATION, 0); + if (this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); this->Window::Close(); } @@ -795,6 +797,8 @@ struct BuildRailToolbarWindow : Window { if (this->IsWidgetLowered(WID_RAT_BUILD_STATION)) SetViewportCatchmentStation(nullptr, true); if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) SetViewportCatchmentWaypoint(nullptr, true); + if (this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); + this->RaiseButtons(); this->DisableWidget(WID_RAT_REMOVE); this->SetWidgetDirty(WID_RAT_REMOVE); @@ -1750,6 +1754,11 @@ struct BuildRailDepotWindow : public PickerWindowBase { break; } } + + void OnRealtimeTick([[maybe_unused]] uint delta_ms) override + { + CheckRedrawDepotHighlight(this, VEH_TRAIN); + } }; /** Nested widget definition of the build rail depot window */ diff --git a/src/road_gui.cpp b/src/road_gui.cpp index 3f9b9251cf..14764444b8 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" @@ -368,6 +369,7 @@ struct BuildRoadToolbarWindow : Window { { if (_game_mode == GM_NORMAL && (this->IsWidgetLowered(WID_ROT_BUS_STATION) || this->IsWidgetLowered(WID_ROT_TRUCK_STATION))) SetViewportCatchmentStation(nullptr, true); if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false); + if (_game_mode == GM_NORMAL && this->IsWidgetLowered(WID_ROT_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); this->Window::Close(); } @@ -673,6 +675,8 @@ struct BuildRoadToolbarWindow : Window { { if (_game_mode != GM_EDITOR && (this->IsWidgetLowered(WID_ROT_BUS_STATION) || this->IsWidgetLowered(WID_ROT_TRUCK_STATION))) SetViewportCatchmentStation(nullptr, true); + if (_game_mode == GM_NORMAL && this->IsWidgetLowered(WID_ROT_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); + this->RaiseButtons(); this->SetWidgetDisabledState(WID_ROT_REMOVE, true); this->SetWidgetDirty(WID_ROT_REMOVE); @@ -1154,6 +1158,11 @@ struct BuildRoadDepotWindow : public PickerWindowBase { break; } } + + void OnRealtimeTick([[maybe_unused]] uint delta_ms) override + { + CheckRedrawDepotHighlight(this, VEH_ROAD); + } }; static constexpr NWidgetPart _nested_build_road_depot_widgets[] = { diff --git a/src/viewport.cpp b/src/viewport.cpp index 1aa848058e..fbe06c45e3 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -90,6 +90,7 @@ #include "network/network_func.h" #include "framerate_type.h" #include "viewport_cmd.h" +#include "depot_map.h" #include #include @@ -1002,6 +1003,7 @@ enum TileHighlightType { const Station *_viewport_highlight_station; ///< Currently selected station for coverage area highlight const Waypoint *_viewport_highlight_waypoint; ///< Currently selected waypoint for coverage area highlight const Town *_viewport_highlight_town; ///< Currently selected town for coverage area highlight +DepotID _viewport_highlight_depot = INVALID_DEPOT; ///< Currently selected depot for depot highlight /** * Get tile highlight type of coverage area for a given tile. @@ -1010,6 +1012,10 @@ const Town *_viewport_highlight_town; ///< Currently selected town for coverage */ static TileHighlightType GetTileHighlightType(TileIndex t) { + if (_viewport_highlight_depot != INVALID_DEPOT) { + if (IsDepotTile(t) && GetDepotIndex(t) == _viewport_highlight_depot) return THT_WHITE; + } + 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; @@ -3589,6 +3595,9 @@ void MarkCatchmentTilesDirty() } MarkWholeScreenDirty(); } + if (_viewport_highlight_depot != INVALID_DEPOT) { + MarkWholeScreenDirty(); + } } static void SetWindowDirtyForViewportCatchment() @@ -3596,6 +3605,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 +3614,7 @@ static void ClearViewportCatchment() _viewport_highlight_station = nullptr; _viewport_highlight_waypoint = nullptr; _viewport_highlight_town = nullptr; + _viewport_highlight_depot = INVALID_DEPOT; } /** @@ -3665,3 +3676,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/widgets/depot_widget.h b/src/widgets/depot_widget.h index 5c98203a03..8797fb36ff 100644 --- a/src/widgets/depot_widget.h +++ b/src/widgets/depot_widget.h @@ -27,6 +27,7 @@ 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. From 914cc0fe96f098d9945687f1ce39d3a38bde166c Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Wed, 31 Mar 2021 23:20:44 +0200 Subject: [PATCH 16/78] Add: Add new viewport place methods for rectangles with one side with a fixed length. --- src/tilehighlight_func.h | 2 ++ src/tilehighlight_type.h | 5 +++-- src/viewport.cpp | 29 +++++++++++++++++++++++------ src/viewport_type.h | 2 ++ 4 files changed, 30 insertions(+), 8 deletions(-) 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/viewport.cpp b/src/viewport.cpp index fbe06c45e3..5e60dae16a 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -2680,6 +2680,8 @@ void UpdateTileSelection() } _thd.new_pos.x = x1 & ~TILE_UNIT_MASK; _thd.new_pos.y = y1 & ~TILE_UNIT_MASK; + if (_thd.select_method == VPM_LIMITED_X_FIXED_Y) _thd.new_size.y = (TILE_SIZE * _thd.fixed_size) & ~TILE_UNIT_MASK; + if (_thd.select_method == VPM_LIMITED_Y_FIXED_X) _thd.new_size.x = (TILE_SIZE * _thd.fixed_size) & ~TILE_UNIT_MASK; } } @@ -2772,6 +2774,15 @@ void VpSetPlaceSizingLimit(int limit) _thd.sizelimit = limit; } +void VpSetPlaceFixedSize(uint8_t fixed) +{ + _thd.fixed_size = fixed; +} + +void VpResetFixedSize() { + VpSetPlaceFixedSize(1); +} + /** * Highlights all tiles between a set of two tiles. Used in dock and tunnel placement * @param from TileIndex of the first tile to highlight @@ -3243,7 +3254,7 @@ void VpSelectTilesWithMethod(int x, int y, ViewportPlaceMethod method) sx = _thd.selstart.x; sy = _thd.selstart.y; - int limit = 0; + int limit = -1; switch (method) { case VPM_X_OR_Y: // drag in X or Y direction @@ -3256,28 +3267,33 @@ void VpSelectTilesWithMethod(int x, int y, ViewportPlaceMethod method) } goto calc_heightdiff_single_direction; + case VPM_LIMITED_Y_FIXED_X: case VPM_X_LIMITED: // Drag in X direction (limited size). limit = (_thd.sizelimit - 1) * TILE_SIZE; [[fallthrough]]; case VPM_FIX_X: // drag in Y direction - x = sx; + x = sx + (method == VPM_LIMITED_Y_FIXED_X ? (TILE_SIZE * (_thd.fixed_size - 1)) : 0) ; style = HT_DIR_Y; goto calc_heightdiff_single_direction; + case VPM_LIMITED_X_FIXED_Y: case VPM_Y_LIMITED: // Drag in Y direction (limited size). limit = (_thd.sizelimit - 1) * TILE_SIZE; [[fallthrough]]; case VPM_FIX_Y: // drag in X direction - y = sy; + y = sy + (method == VPM_LIMITED_X_FIXED_Y ? (TILE_SIZE * (_thd.fixed_size - 1)) : 0) ; style = HT_DIR_X; calc_heightdiff_single_direction:; - if (limit > 0) { - x = sx + Clamp(x - sx, -limit, limit); - y = sy + Clamp(y - sy, -limit, limit); + if (limit >= 0) { + if (method != VPM_LIMITED_X_FIXED_Y) y = sy + Clamp(y - sy, -limit, limit); + if (method != VPM_LIMITED_Y_FIXED_X) x = sx + Clamp(x - sx, -limit, limit); } + + if (method == VPM_LIMITED_Y_FIXED_X || method == VPM_LIMITED_X_FIXED_Y) goto measure_area; + if (_settings_client.gui.measure_tooltip) { TileIndex t0 = TileVirtXY(sx, sy); TileIndex t1 = TileVirtXY(x, y); @@ -3307,6 +3323,7 @@ calc_heightdiff_single_direction:; [[fallthrough]]; case VPM_X_AND_Y: // drag an X by Y area +measure_area: if (_settings_client.gui.measure_tooltip) { static const StringID measure_strings_area[] = { STR_NULL, STR_NULL, STR_MEASURE_AREA, STR_MEASURE_AREA_HEIGHTDIFF diff --git a/src/viewport_type.h b/src/viewport_type.h index 0fde051f38..b026650db9 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 }; From 9f916f31c76b79fcfc1b0f6dc368ead0cc12a763 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Mon, 8 Apr 2024 19:03:17 +0200 Subject: [PATCH 17/78] Feature: Allow building depots by drag and drop and joining them if they have the same transport type. --- src/dock_gui.cpp | 28 ++++++- src/rail_cmd.cpp | 138 ++++++++++++++++++------------- src/rail_cmd.h | 4 +- src/rail_gui.cpp | 44 +++++++--- src/road_cmd.cpp | 102 +++++++++++++---------- src/road_cmd.h | 5 +- src/road_gui.cpp | 28 +++++-- src/roadveh_cmd.cpp | 42 +++++++++- src/script/api/script_marine.cpp | 2 +- src/script/api/script_rail.cpp | 2 +- src/script/api/script_road.cpp | 2 +- src/train_gui.cpp | 4 +- src/viewport.cpp | 2 +- src/viewport_type.h | 1 + src/water_cmd.cpp | 104 ++++++++++++----------- src/water_cmd.h | 2 +- 16 files changed, 329 insertions(+), 181 deletions(-) diff --git a/src/dock_gui.cpp b/src/dock_gui.cpp index 7ba951d141..2cc37266a5 100644 --- a/src/dock_gui.cpp +++ b/src/dock_gui.cpp @@ -167,7 +167,9 @@ struct BuildDocksToolbarWindow : Window { break; case WID_DT_DEPOT: // Build depot button - if (HandlePlacePushButton(this, WID_DT_DEPOT, SPR_CURSOR_SHIP_DEPOT, HT_RECT)) ShowBuildDocksDepotPicker(this); + if (HandlePlacePushButton(this, widget, SPR_CURSOR_SHIP_DEPOT, HT_RECT)) { + ShowBuildDocksDepotPicker(this); + } break; case WID_DT_STATION: // Build station button @@ -207,9 +209,16 @@ struct BuildDocksToolbarWindow : Window { PlaceProc_DemolishArea(tile); break; - case WID_DT_DEPOT: // Build depot button - Command::Post(STR_ERROR_CAN_T_BUILD_SHIP_DEPOT, CcBuildDocks, tile, _ship_depot_direction); + case WID_DT_DEPOT: { // Build depot button + CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP); + + ViewportPlaceMethod vpm = _ship_depot_direction != AXIS_X ? VPM_LIMITED_X_FIXED_Y : VPM_LIMITED_Y_FIXED_X; + VpSetPlaceSizingLimit(_settings_game.depot.depot_spread); + VpStartPlaceSizing(tile, vpm, DDSP_BUILD_DEPOT); + /* Select tiles now to prevent selection from flickering. */ + VpSelectTilesWithMethod(pt.x, pt.y, vpm); break; + } case WID_DT_STATION: { // Build station button /* Determine the watery part of the dock. */ @@ -263,6 +272,15 @@ struct BuildDocksToolbarWindow : Window { case DDSP_CREATE_RIVER: Command::Post(STR_ERROR_CAN_T_PLACE_RIVERS, CcPlaySound_CONSTRUCTION_WATER, end_tile, start_tile, WATER_CLASS_RIVER, _ctrl_pressed); break; + case DDSP_BUILD_DEPOT: { + bool adjacent = _ctrl_pressed; + auto proc = [=](DepotID join_to) -> bool { + return Command::Post(STR_ERROR_CAN_T_BUILD_SHIP_DEPOT, CcBuildDocks, start_tile, _ship_depot_direction, adjacent, join_to, end_tile); + }; + + ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_SHIP); + break; + } default: break; } @@ -520,10 +538,13 @@ struct BuildDocksDepotWindow : public PickerWindowBase { private: static void UpdateDocksDirection() { + VpSetPlaceFixedSize(2); if (_ship_depot_direction != AXIS_X) { SetTileSelectSize(1, 2); + _thd.select_method = VPM_LIMITED_X_FIXED_Y; } else { SetTileSelectSize(2, 1); + _thd.select_method = VPM_LIMITED_Y_FIXED_X; } } @@ -538,6 +559,7 @@ public: void Close([[maybe_unused]] int data = 0) override { CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP); + VpResetFixedSize(); this->PickerWindowBase::Close(); } diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 91dff791db..89554c655c 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -952,22 +952,32 @@ CommandCost CmdRemoveRailroadTrack(DoCommandFlag flags, TileIndex end_tile, Tile /** * Build a train depot * @param flags operation to perform - * @param tile position of the train depot + * @param tile first position of the train depot * @param railtype rail type * @param dir entrance direction + * @param adjacent allow adjacent depots + * @param join_to depot to join to + * @param end_tile end tile of the area to be built * @return the cost of this operation or an error * * @todo When checking for the tile slope, * distinguish between "Flat land required" and "land sloped in wrong direction" */ -CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir) +CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir, bool adjacent, DepotID join_to, TileIndex end_tile) { /* check railtype and valid direction for depot (0 through 3), 4 in total */ if (!ValParamRailType(railtype) || !IsValidDiagDirection(dir)) return CMD_ERROR; - Slope tileh = GetTileSlope(tile); - CommandCost cost(EXPENSES_CONSTRUCTION); + TileArea ta(tile, end_tile); + Depot *depot = nullptr; + + /* Create a new depot or find a depot to join to. */ + CommandCost ret = FindJoiningDepot(ta, VEH_TRAIN, join_to, depot, adjacent, flags); + if (ret.Failed()) return ret; + + uint8_t num_new_depot_tiles = 0; + uint8_t num_rotated_depot_tiles = 0; /* Prohibit construction if * The tile is non-flat AND @@ -975,58 +985,59 @@ CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType rai * 2) the tile is steep i.e. spans two height levels * 3) the exit points in the wrong direction */ + for (Tile t : ta) { + if (IsBridgeAbove(t)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); - if (tileh != SLOPE_FLAT) { - if (!_settings_game.construction.build_on_slopes || !CanBuildDepotByTileh(dir, tileh)) { - return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + Slope tileh = GetTileSlope(t); + if (tileh != SLOPE_FLAT) { + if (!_settings_game.construction.build_on_slopes || + !CanBuildDepotByTileh(dir, tileh)) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } + cost.AddCost(_price[PR_BUILD_FOUNDATION]); + } + + /* Check whether a depot tile exists and it needs to be rotated. */ + if (IsRailDepotTile(t) && GetDepotIndex(t) == join_to && railtype == GetRailType(t)) { + if (dir == GetRailDepotDirection(t)) continue; + + ret = EnsureNoVehicleOnGround(t); + if (ret.Failed()) return ret; + + num_rotated_depot_tiles++; + if (flags & DC_EXEC) { + SetRailDepotExitDirection(t, dir); + } + } else { + cost.AddCost(Command::Do(flags, t)); + if (cost.Failed()) return cost; + + num_new_depot_tiles++; + if (flags & DC_EXEC) { + MakeRailDepot(t, _current_company, depot->index, dir, railtype); + MarkTileDirtyByTile(t); + } + } + + if (flags & DC_EXEC) { + AddSideToSignalBuffer(t, INVALID_DIAGDIR, _current_company); + YapfNotifyTrackLayoutChange(t, DiagDirToDiagTrack(dir)); + MarkTileDirtyByTile(t); } - cost.AddCost(_price[PR_BUILD_FOUNDATION]); } - /* Allow the user to rotate the depot instead of having to destroy it and build it again */ - bool rotate_existing_depot = false; - if (IsRailDepotTile(tile) && railtype == GetRailType(tile)) { - CommandCost ret = CheckTileOwnership(tile); - if (ret.Failed()) return ret; + if (num_new_depot_tiles + num_rotated_depot_tiles == 0) return CommandCost(); - if (dir == GetRailDepotDirection(tile)) return CommandCost(); - - ret = EnsureNoVehicleOnGround(tile); - if (ret.Failed()) return ret; - - rotate_existing_depot = true; - } - - if (!rotate_existing_depot) { - cost.AddCost(Command::Do(flags, tile)); - if (cost.Failed()) return cost; - - if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); - - if (!Depot::CanAllocateItem()) return CMD_ERROR; - } + cost.AddCost(_price[PR_BUILD_DEPOT_TRAIN] * (num_new_depot_tiles + num_rotated_depot_tiles)); + cost.AddCost(RailBuildCost(railtype) * (num_new_depot_tiles + num_rotated_depot_tiles)); if (flags & DC_EXEC) { - if (rotate_existing_depot) { - SetRailDepotExitDirection(tile, dir); - } else { - Depot *d = new Depot(tile, VEH_TRAIN, _current_company); - d->build_date = TimerGameCalendar::date; - - MakeRailDepot(tile, _current_company, d->index, dir, railtype); - MakeDefaultName(d); - - Company::Get(_current_company)->infrastructure.rail[railtype]++; - DirtyCompanyInfrastructureWindows(_current_company); - } - - MarkTileDirtyByTile(tile); - AddSideToSignalBuffer(tile, INVALID_DIAGDIR, _current_company); - YapfNotifyTrackLayoutChange(tile, DiagDirToDiagTrack(dir)); + Company::Get(_current_company)->infrastructure.rail[railtype] += num_new_depot_tiles; + DirtyCompanyInfrastructureWindows(_current_company); + depot->AfterAddRemove(ta, true); + if (join_to == NEW_DEPOT) MakeDefaultName(depot); } - cost.AddCost(_price[PR_BUILD_DEPOT_TRAIN]); - cost.AddCost(RailBuildCost(railtype)); return cost; } @@ -1558,6 +1569,7 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s if (area_start >= Map::Size()) return CMD_ERROR; TrainList affected_trains; + std::vector affected_depots; CommandCost cost(EXPENSES_CONSTRUCTION); CommandCost error = CommandCost(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); // by default, there is no track to convert. @@ -1653,11 +1665,11 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s /* notify YAPF about the track layout change */ YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile)); - /* Update build vehicle window related to this depot */ - DepotID depot_id = GetDepotIndex(tile); - InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); - InvalidateWindowData(WC_BUILD_VEHICLE, depot_id); + if (find(affected_depots.begin(), affected_depots.end(), (tile)) == affected_depots.end()) { + affected_depots.push_back(GetDepotIndex(tile)); + } } + found_convertible_track = true; cost.AddCost(RailConvertCost(type, totype)); break; @@ -1755,6 +1767,13 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s } if (flags & DC_EXEC) { + /* Update affected depots. */ + for (auto &depot_tile : affected_depots) { + Depot *dep = Depot::Get(depot_tile); + dep->RescanDepotTiles(); + InvalidateWindowData(WC_VEHICLE_DEPOT, dep->index); + } + /* Railtype changed, update trains as when entering different track */ for (Train *v : affected_trains) { v->ConsistChanged(CCF_TRACK); @@ -1775,9 +1794,11 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) if (ret.Failed()) return ret; if (flags & DC_EXEC) { - /* read variables before the depot is removed */ + Depot *depot = Depot::GetByTile(tile); + Company *c = Company::GetIfValid(depot->owner); + assert(c != nullptr); + DiagDirection dir = GetRailDepotDirection(tile); - Owner owner = GetTileOwner(tile); Train *v = nullptr; if (HasDepotReservation(tile)) { @@ -1785,14 +1806,17 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) if (v != nullptr) FreeTrainTrackReservation(v); } - Company::Get(owner)->infrastructure.rail[GetRailType(tile)]--; - DirtyCompanyInfrastructureWindows(owner); + c->infrastructure.rail[GetRailType(tile)]--; + DirtyCompanyInfrastructureWindows(c->index); - delete Depot::GetByTile(tile); DoClearSquare(tile); - AddSideToSignalBuffer(tile, dir, owner); + + AddSideToSignalBuffer(tile, dir, c->index); + YapfNotifyTrackLayoutChange(tile, DiagDirToDiagTrack(dir)); if (v != nullptr) TryPathReserve(v, true); + + depot->AfterAddRemove(TileArea(tile), false); } return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_TRAIN]); diff --git a/src/rail_cmd.h b/src/rail_cmd.h index b9f6c0cc03..ce10c4fd77 100644 --- a/src/rail_cmd.h +++ b/src/rail_cmd.h @@ -19,7 +19,7 @@ CommandCost CmdBuildRailroadTrack(DoCommandFlag flags, TileIndex end_tile, TileI CommandCost CmdRemoveRailroadTrack(DoCommandFlag flags, TileIndex end_tile, TileIndex start_tile, Track track); CommandCost CmdBuildSingleRail(DoCommandFlag flags, TileIndex tile, RailType railtype, Track track, bool auto_remove_signals); CommandCost CmdRemoveSingleRail(DoCommandFlag flags, TileIndex tile, Track track); -CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir); +CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir, bool adjacent, DepotID depot_id, TileIndex end_tile); CommandCost CmdBuildSingleSignal(DoCommandFlag flags, TileIndex tile, Track track, SignalType sigtype, SignalVariant sigvar, bool convert_signal, bool skip_existing_signals, bool ctrl_pressed, SignalType cycle_start, SignalType cycle_stop, uint8_t num_dir_cycle, uint8_t signals_copy); CommandCost CmdRemoveSingleSignal(DoCommandFlag flags, TileIndex tile, Track track); CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_start, RailType totype, bool diagonal); @@ -40,6 +40,6 @@ DEF_CMD_TRAIT(CMD_REMOVE_SIGNAL_TRACK, CmdRemoveSignalTrack, CMD_AUTO, CommandCallback CcPlaySound_CONSTRUCTION_RAIL; CommandCallback CcStation; CommandCallback CcBuildRailTunnel; -void CcRailDepot(Commands cmd, const CommandCost &result, TileIndex tile, RailType rt, DiagDirection dir); +void CcRailDepot(Commands cmd, const CommandCost &result, TileIndex tile, RailType rt, DiagDirection dir, bool adjacent, DepotID join_to, TileIndex end_tile); #endif /* RAIL_CMD_H */ diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index dcb0780919..271700e8c8 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -117,7 +117,7 @@ static void GenericPlaceRail(TileIndex tile, Track track) */ static void PlaceExtraDepotRail(TileIndex tile, DiagDirection dir, Track track) { - if (GetRailTileType(tile) == RAIL_TILE_DEPOT) return; + if (IsRailDepot(tile)) return; if (GetRailTileType(tile) == RAIL_TILE_SIGNALS && !_settings_client.gui.auto_remove_signals) return; if ((GetTrackBits(tile) & DiagdirReachesTracks(dir)) == 0) return; @@ -138,24 +138,26 @@ static const DiagDirection _place_depot_extra_dir[12] = { DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_NW, DIAGDIR_NE, }; -void CcRailDepot(Commands, const CommandCost &result, TileIndex tile, RailType, DiagDirection dir) +void CcRailDepot(Commands, const CommandCost &result, TileIndex start_tile, RailType, DiagDirection dir, bool, DepotID, TileIndex end_tile) { if (result.Failed()) return; - if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, tile); + if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, start_tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); - tile += TileOffsByDiagDir(dir); + TileArea ta(start_tile, end_tile); + for (TileIndex t : ta) { + TileIndex tile = t + TileOffsByDiagDir(dir); - if (IsTileType(tile, MP_RAILWAY)) { - PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir], _place_depot_extra_track[dir]); + if (IsTileType(tile, MP_RAILWAY)) { + PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir], _place_depot_extra_track[dir]); - /* Don't place the rail straight out of the depot of there is another depot across from it. */ - Tile double_depot_tile = tile + TileOffsByDiagDir(dir); - bool is_double_depot = IsValidTile(double_depot_tile) && IsRailDepotTile(double_depot_tile); - if (!is_double_depot) PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 4], _place_depot_extra_track[dir + 4]); + Tile double_depot_tile = tile + TileOffsByDiagDir(dir); + bool is_double_depot = IsValidTile(double_depot_tile) && IsRailDepotTile(double_depot_tile); + if (!is_double_depot) PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 4], _place_depot_extra_track[dir + 4]); - PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 8], _place_depot_extra_track[dir + 8]); + PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 8], _place_depot_extra_track[dir + 8]); + } } } @@ -690,9 +692,14 @@ struct BuildRailToolbarWindow : Window { PlaceProc_DemolishArea(tile); break; - case WID_RAT_BUILD_DEPOT: - Command::Post(STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT, CcRailDepot, tile, _cur_railtype, _build_depot_direction); + case WID_RAT_BUILD_DEPOT: { + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); + + ViewportPlaceMethod vpm = (DiagDirToAxis(_build_depot_direction) == 0) ? VPM_X_LIMITED : VPM_Y_LIMITED; + VpStartPlaceSizing(tile, vpm, DDSP_BUILD_DEPOT); + VpSetPlaceSizingLimit(_settings_game.depot.depot_spread); break; + } case WID_RAT_BUILD_WAYPOINT: PlaceRail_Waypoint(tile); @@ -788,6 +795,17 @@ struct BuildRailToolbarWindow : Window { } } break; + + case DDSP_BUILD_DEPOT: { + bool adjacent = _ctrl_pressed; + + auto proc = [=](DepotID join_to) -> bool { + return Command::Post(STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT, CcRailDepot, start_tile, _cur_railtype, _build_depot_direction, adjacent, join_to, end_tile); + }; + + ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_TRAIN); + break; + } } } } diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 7760a90fbe..e6a933ce59 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -1141,66 +1141,79 @@ std::tuple CmdRemoveLongRoad(DoCommandFlag flags, TileIndex * @param flags operation to perform * @param rt road type * @param dir entrance direction + * @param adjacent allow adjacent depots + * @param depot_id depot to join to + * @param end_tile end tile of the depot to be built * @return the cost of this operation or an error * * @todo When checking for the tile slope, * distinguish between "Flat land required" and "land sloped in wrong direction" */ -CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, DiagDirection dir) +CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, DiagDirection dir, bool adjacent, DepotID join_to, TileIndex end_tile) { if (!ValParamRoadType(rt) || !IsValidDiagDirection(dir)) return CMD_ERROR; + TileArea ta(tile, end_tile); + assert(ta.w == 1 || ta.h == 1); + + /* Create a new depot or find a depot to join to. */ + Depot *depot = nullptr; + CommandCost ret = FindJoiningDepot(ta, VEH_ROAD, join_to, depot, adjacent, flags); + if (ret.Failed()) return ret; + + uint8_t num_new_depot_tiles = 0; + uint8_t num_rotated_depot_tiles = 0; + CommandCost cost(EXPENSES_CONSTRUCTION); + for (Tile t : ta) { + if (IsBridgeAbove(t)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); - Slope tileh = GetTileSlope(tile); - if (tileh != SLOPE_FLAT) { - if (!_settings_game.construction.build_on_slopes || !CanBuildDepotByTileh(dir, tileh)) { - return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + Slope tileh = GetTileSlope(t); + if (tileh != SLOPE_FLAT) { + if (!_settings_game.construction.build_on_slopes || !CanBuildDepotByTileh(dir, tileh)) { + return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } + cost.AddCost(_price[PR_BUILD_FOUNDATION]); + } + + /* Check whether a depot tile exists and it needs to be rotated. */ + if (IsRoadDepotTile(t) && + GetDepotIndex(t) == join_to && + (HasRoadTypeTram(t) ? rt == GetRoadTypeTram(t) : rt == GetRoadTypeRoad(t))) { + if (dir == GetRoadDepotDirection(t)) continue; + + ret = EnsureNoVehicleOnGround(t); + if (ret.Failed()) return ret; + + num_rotated_depot_tiles++; + if (flags & DC_EXEC) { + SetRoadDepotExitDirection(t, dir); + MarkTileDirtyByTile(t); + } + + } else { + cost.AddCost(Command::Do(flags, t)); + if (cost.Failed()) return cost; + + num_new_depot_tiles++; + if (flags & DC_EXEC) { + MakeRoadDepot(t, _current_company, depot->index, dir, rt); + MarkTileDirtyByTile(t); + } } - cost.AddCost(_price[PR_BUILD_FOUNDATION]); } - /* Allow the user to rotate the depot instead of having to destroy it and build it again */ - bool rotate_existing_depot = false; - if (IsRoadDepotTile(tile) && (HasRoadTypeTram(tile) ? rt == GetRoadTypeTram(tile) : rt == GetRoadTypeRoad(tile))) - { - CommandCost ret = CheckTileOwnership(tile); - if (ret.Failed()) return ret; - - if (dir == GetRoadDepotDirection(tile)) return CommandCost(); - - ret = EnsureNoVehicleOnGround(tile); - if (ret.Failed()) return ret; - - rotate_existing_depot = true; - } - - if (!rotate_existing_depot) { - cost.AddCost(Command::Do(flags, tile)); - if (cost.Failed()) return cost; - - if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); - - if (!Depot::CanAllocateItem()) return CMD_ERROR; - } + if (num_new_depot_tiles + num_rotated_depot_tiles == 0) return CommandCost(); if (flags & DC_EXEC) { - if (rotate_existing_depot) { - SetRoadDepotExitDirection(tile, dir); - } else { - Depot *dep = new Depot(tile, VEH_ROAD, _current_company); - dep->build_date = TimerGameCalendar::date; - MakeRoadDepot(tile, _current_company, dep->index, dir, rt); - MakeDefaultName(dep); + /* A road depot has two road bits. */ + UpdateCompanyRoadInfrastructure(rt, _current_company, num_new_depot_tiles * ROAD_DEPOT_TRACKBIT_FACTOR); - /* A road depot has two road bits. */ - UpdateCompanyRoadInfrastructure(rt, _current_company, ROAD_DEPOT_TRACKBIT_FACTOR); - } - - MarkTileDirtyByTile(tile); + depot->AfterAddRemove(ta, true); + if (join_to == NEW_DEPOT) MakeDefaultName(depot); } - cost.AddCost(_price[PR_BUILD_DEPOT_ROAD]); + cost.AddCost(_price[PR_BUILD_DEPOT_ROAD] * (num_new_depot_tiles + num_rotated_depot_tiles)); return cost; } @@ -1215,7 +1228,8 @@ static CommandCost RemoveRoadDepot(TileIndex tile, DoCommandFlag flags) if (ret.Failed()) return ret; if (flags & DC_EXEC) { - Company *c = Company::GetIfValid(GetTileOwner(tile)); + Depot *depot = Depot::GetByTile(tile); + Company *c = Company::GetIfValid(depot->owner); if (c != nullptr) { /* A road depot has two road bits. */ RoadType rt = GetRoadTypeRoad(tile); @@ -1224,8 +1238,8 @@ static CommandCost RemoveRoadDepot(TileIndex tile, DoCommandFlag flags) DirtyCompanyInfrastructureWindows(c->index); } - delete Depot::GetByTile(tile); DoClearSquare(tile); + depot->AfterAddRemove(TileArea(tile), false); } return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_ROAD]); diff --git a/src/road_cmd.h b/src/road_cmd.h index 71883ddada..2ae7e170e6 100644 --- a/src/road_cmd.h +++ b/src/road_cmd.h @@ -13,6 +13,7 @@ #include "direction_type.h" #include "road_type.h" #include "command_type.h" +#include "depot_type.h" enum RoadStopClassID : uint16_t; @@ -22,7 +23,7 @@ void UpdateNearestTownForRoadTiles(bool invalidate); CommandCost CmdBuildLongRoad(DoCommandFlag flags, TileIndex end_tile, TileIndex start_tile, RoadType rt, Axis axis, DisallowedRoadDirections drd, bool start_half, bool end_half, bool is_ai); std::tuple CmdRemoveLongRoad(DoCommandFlag flags, TileIndex end_tile, TileIndex start_tile, RoadType rt, Axis axis, bool start_half, bool end_half); CommandCost CmdBuildRoad(DoCommandFlag flags, TileIndex tile, RoadBits pieces, RoadType rt, DisallowedRoadDirections toggle_drd, TownID town_id); -CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, DiagDirection dir); +CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, DiagDirection dir, bool adjacent, DepotID join_to, TileIndex end_tile); CommandCost CmdConvertRoad(DoCommandFlag flags, TileIndex tile, TileIndex area_start, RoadType to_type); DEF_CMD_TRAIT(CMD_BUILD_LONG_ROAD, CmdBuildLongRoad, CMD_AUTO | CMD_NO_WATER | CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION) @@ -33,7 +34,7 @@ DEF_CMD_TRAIT(CMD_CONVERT_ROAD, CmdConvertRoad, 0, CommandCallback CcPlaySound_CONSTRUCTION_OTHER; CommandCallback CcBuildRoadTunnel; -void CcRoadDepot(Commands cmd, const CommandCost &result, TileIndex tile, RoadType rt, DiagDirection dir); +void CcRoadDepot(Commands cmd, const CommandCost &result, TileIndex start_tile, RoadType rt, DiagDirection dir, bool adjacent, DepotID join_to, TileIndex end_tile); void CcRoadStop(Commands cmd, const CommandCost &result, TileIndex tile, uint8_t width, uint8_t length, RoadStopType, bool is_drive_through, DiagDirection dir, RoadType, RoadStopClassID spec_class, uint16_t spec_index, StationID, bool); #endif /* ROAD_CMD_H */ diff --git a/src/road_gui.cpp b/src/road_gui.cpp index 14764444b8..53a71d0d2c 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -171,13 +171,17 @@ void ConnectRoadToStructure(TileIndex tile, DiagDirection direction) } } -void CcRoadDepot(Commands, const CommandCost &result, TileIndex tile, RoadType, DiagDirection dir) +void CcRoadDepot(Commands, const CommandCost &result, TileIndex start_tile, RoadType, DiagDirection dir, bool, DepotID, TileIndex end_tile) { if (result.Failed()) return; - if (_settings_client.sound.confirm) SndPlayTileFx(SND_1F_CONSTRUCTION_OTHER, tile); + if (_settings_client.sound.confirm) SndPlayTileFx(SND_1F_CONSTRUCTION_OTHER, start_tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); - ConnectRoadToStructure(tile, dir); + + TileArea ta(start_tile, end_tile); + for (TileIndex tile : ta) { + ConnectRoadToStructure(tile, dir); + } } /** @@ -638,8 +642,10 @@ struct BuildRoadToolbarWindow : Window { break; case WID_ROT_DEPOT: - Command::Post(this->rti->strings.err_depot, CcRoadDepot, - tile, _cur_roadtype, _road_depot_orientation); + CloseWindowById(WC_SELECT_DEPOT, VEH_ROAD); + + VpSetPlaceSizingLimit(_settings_game.depot.depot_spread); + VpStartPlaceSizing(tile, (DiagDirToAxis(_road_depot_orientation) == 0) ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_DEPOT); break; case WID_ROT_BUILD_WAYPOINT: @@ -810,6 +816,18 @@ struct BuildRoadToolbarWindow : Window { } break; + case DDSP_BUILD_DEPOT: { + bool adjacent = _ctrl_pressed; + StringID error_string = this->rti->strings.err_depot; + + auto proc = [=](DepotID join_to) -> bool { + return Command::Post(error_string, CcRoadDepot, start_tile, _cur_roadtype, _road_depot_orientation, adjacent, join_to, end_tile); + }; + + ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_ROAD); + break; + } + case DDSP_CONVERT_ROAD: Command::Post(rti->strings.err_convert_road, CcPlaySound_CONSTRUCTION_OTHER, end_tile, start_tile, _cur_roadtype); break; diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 2ff9258374..37cfc04633 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -37,6 +37,7 @@ #include "framerate_type.h" #include "roadveh_cmd.h" #include "road_cmd.h" +#include "depot_base.h" #include "table/strings.h" @@ -250,6 +251,41 @@ void RoadVehUpdateCache(RoadVehicle *v, bool same_length) v->vcache.cached_max_speed = (max_speed != 0) ? max_speed * 4 : RoadVehInfo(v->engine_type)->max_speed; } +/** + * Find an adequate tile for placing an engine. + * @param[in,out] tile A tile of the depot. + * @param e Engine to be built. + * @param flags Flags of the command. + * @return CommandCost() or an error message if the depot is not appropriate. + */ +CommandCost FindDepotTileForPlacingEngine(TileIndex &tile, const Engine *e, DoCommandFlag flags) +{ + assert(IsRoadDepotTile(tile)); + + Depot *dep = Depot:: GetByTile(tile); + + /* Check that the vehicle can drive on some tile of the depot */ + RoadType rt = e->u.road.roadtype; + const RoadTypeInfo *rti = GetRoadTypeInfo(rt); + if ((dep->r_types.road_types & rti->powered_roadtypes) == 0) return_cmd_error(STR_ERROR_DEPOT_WRONG_DEPOT_TYPE); + + /* Use same tile if possible when replacing. */ + if (flags & DC_AUTOREPLACE) { + /* Use same tile if possible when replacing. */ + if (HasTileAnyRoadType(tile, rti->powered_roadtypes)) return CommandCost(); + } + + tile = INVALID_TILE; + for (auto &depot_tile : dep->depot_tiles) { + if (!HasTileAnyRoadType(depot_tile, rti->powered_roadtypes)) continue; + tile = depot_tile; + break; + } + + assert(tile != INVALID_TILE); + return CommandCost(); +} + /** * Build a road vehicle. * @param flags type of operation. @@ -260,10 +296,12 @@ void RoadVehUpdateCache(RoadVehicle *v, bool same_length) */ CommandCost CmdBuildRoadVehicle(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **ret) { - /* Check that the vehicle can drive on the road in question */ + assert(IsRoadDepotTile(tile)); RoadType rt = e->u.road.roadtype; const RoadTypeInfo *rti = GetRoadTypeInfo(rt); - if (!HasTileAnyRoadType(tile, rti->powered_roadtypes)) return_cmd_error(STR_ERROR_DEPOT_WRONG_DEPOT_TYPE); + + CommandCost check = FindDepotTileForPlacingEngine(tile, e, flags); + if (check.Failed()) return check; if (flags & DC_EXEC) { const RoadVehicleInfo *rvi = &e->u.road; diff --git a/src/script/api/script_marine.cpp b/src/script/api/script_marine.cpp index 6c04e19046..5e6d60a6c6 100644 --- a/src/script/api/script_marine.cpp +++ b/src/script/api/script_marine.cpp @@ -83,7 +83,7 @@ EnforcePrecondition(false, ::IsValidTile(front)); EnforcePrecondition(false, (::TileX(front) == ::TileX(tile)) != (::TileY(front) == ::TileY(tile))); - return ScriptObject::Command::Do(tile, ::TileX(front) == ::TileX(tile) ? AXIS_Y : AXIS_X); + return ScriptObject::Command::Do(tile, ::TileX(front) == ::TileX(tile) ? AXIS_Y : AXIS_X, false, INVALID_DEPOT, tile); } /* static */ bool ScriptMarine::BuildDock(TileIndex tile, StationID station_id) diff --git a/src/script/api/script_rail.cpp b/src/script/api/script_rail.cpp index ac1b9fc5a9..5ac4a9b059 100644 --- a/src/script/api/script_rail.cpp +++ b/src/script/api/script_rail.cpp @@ -145,7 +145,7 @@ DiagDirection entrance_dir = (::TileX(tile) == ::TileX(front)) ? (::TileY(tile) < ::TileY(front) ? DIAGDIR_SE : DIAGDIR_NW) : (::TileX(tile) < ::TileX(front) ? DIAGDIR_SW : DIAGDIR_NE); - return ScriptObject::Command::Do(tile, (::RailType)ScriptObject::GetRailType(), entrance_dir); + return ScriptObject::Command::Do(tile, (::RailType)ScriptObject::GetRailType(), entrance_dir, false, INVALID_DEPOT, tile); } /* static */ bool ScriptRail::BuildRailStation(TileIndex tile, RailTrack direction, SQInteger num_platforms, SQInteger platform_length, StationID station_id) diff --git a/src/script/api/script_road.cpp b/src/script/api/script_road.cpp index 25b7fc2631..9b29e2d4b9 100644 --- a/src/script/api/script_road.cpp +++ b/src/script/api/script_road.cpp @@ -535,7 +535,7 @@ static bool NeighbourHasReachableRoad(::RoadType rt, TileIndex start_tile, DiagD DiagDirection entrance_dir = (::TileX(tile) == ::TileX(front)) ? (::TileY(tile) < ::TileY(front) ? DIAGDIR_SE : DIAGDIR_NW) : (::TileX(tile) < ::TileX(front) ? DIAGDIR_SW : DIAGDIR_NE); - return ScriptObject::Command::Do(tile, ScriptObject::GetRoadType(), entrance_dir); + return ScriptObject::Command::Do(tile, ScriptObject::GetRoadType(), entrance_dir, false, INVALID_DEPOT, tile); } /* static */ bool ScriptRoad::_BuildRoadStationInternal(TileIndex tile, TileIndex front, RoadVehicleType road_veh_type, bool drive_through, StationID station_id) diff --git a/src/train_gui.cpp b/src/train_gui.cpp index d62d3dce55..51ad7a9d00 100644 --- a/src/train_gui.cpp +++ b/src/train_gui.cpp @@ -15,6 +15,7 @@ #include "vehicle_func.h" #include "zoom_func.h" #include "train_cmd.h" +#include "depot_map.h" #include "table/strings.h" @@ -29,11 +30,12 @@ void CcBuildWagon(Commands, const CommandCost &result, VehicleID new_veh_id, uint, uint16_t, CargoArray, TileIndex tile, EngineID, bool, CargoID, ClientID) { if (result.Failed()) return; + DepotID depot_id = GetDepotIndex(tile); /* find a locomotive in the depot. */ const Vehicle *found = nullptr; for (const Train *t : Train::Iterate()) { - if (t->IsFrontEngine() && t->tile == tile && t->IsStoppedInDepot()) { + if (t->IsFrontEngine() && t->IsStoppedInDepot() && GetDepotIndex(t->tile) == depot_id) { if (found != nullptr) return; // must be exactly one. found = t; } diff --git a/src/viewport.cpp b/src/viewport.cpp index 5e60dae16a..6a9da5e44d 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -1013,7 +1013,7 @@ DepotID _viewport_highlight_depot = INVALID_DEPOT; ///< Currently selected depot static TileHighlightType GetTileHighlightType(TileIndex t) { if (_viewport_highlight_depot != INVALID_DEPOT) { - if (IsDepotTile(t) && GetDepotIndex(t) == _viewport_highlight_depot) return THT_WHITE; + if (IsDepotTile(t) && GetDepotIndex(t) == _viewport_highlight_depot) return THT_BLUE; } if (_viewport_highlight_station != nullptr) { diff --git a/src/viewport_type.h b/src/viewport_type.h index b026650db9..a65ec9d9f7 100644 --- a/src/viewport_type.h +++ b/src/viewport_type.h @@ -122,6 +122,7 @@ enum ViewportDragDropSelectionProcess { DDSP_PLANT_TREES, ///< Plant trees DDSP_BUILD_BRIDGE, ///< Bridge placement DDSP_BUILD_OBJECT, ///< Build an object + DDSP_BUILD_DEPOT, ///< Depot placement /* Rail specific actions */ DDSP_PLACE_RAIL, ///< Rail placement diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index 54f943451b..7c265e81cc 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -94,66 +94,75 @@ static void MarkCanalsAndRiversAroundDirty(TileIndex tile) /** * Build a ship depot. * @param flags type of operation - * @param tile tile where ship depot is built + * @param tile first tile where ship depot is built * @param axis depot orientation (Axis) + * @param adjacent allow adjacent depots + * @param join_to depot to join to + * @param end_tile end tile of area to be built * @return the cost of this operation or an error */ -CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis) +CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis, bool adjacent, DepotID join_to, TileIndex end_tile) { if (!IsValidAxis(axis)) return CMD_ERROR; TileIndex tile2 = tile + (axis == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); - if (!HasTileWaterGround(tile) || !HasTileWaterGround(tile2)) { - return_cmd_error(STR_ERROR_MUST_BE_BUILT_ON_WATER); - } + TileArea complete_area(tile, end_tile); + complete_area.Add(tile2); + assert(complete_area.w == 2 || complete_area.h == 2); - if (IsBridgeAbove(tile) || IsBridgeAbove(tile2)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + TileArea northern_tiles(complete_area.tile); + northern_tiles.Add(complete_area.tile + (axis == AXIS_X ? TileDiffXY(0, complete_area.h - 1) : TileDiffXY(complete_area.w - 1, 0))); - if (!IsTileFlat(tile) || !IsTileFlat(tile2)) { - /* Prevent depots on rapids */ - return_cmd_error(STR_ERROR_SITE_UNSUITABLE); - } - - if (!Depot::CanAllocateItem()) return CMD_ERROR; - - WaterClass wc1 = GetWaterClass(tile); - WaterClass wc2 = GetWaterClass(tile2); - CommandCost cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_DEPOT_SHIP]); - - bool add_cost = !IsWaterTile(tile); - CommandCost ret = Command::Do(flags | DC_AUTO, tile); + /* Create a new depot or find a depot to join to. */ + Depot *depot = nullptr; + CommandCost ret = FindJoiningDepot(complete_area, VEH_SHIP, join_to, depot, adjacent, flags); if (ret.Failed()) return ret; - if (add_cost) { - cost.AddCost(ret); - } - add_cost = !IsWaterTile(tile2); - ret = Command::Do(flags | DC_AUTO, tile2); - if (ret.Failed()) return ret; - if (add_cost) { - cost.AddCost(ret); + + /* Get the cost of building all the ship depots. */ + CommandCost cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_DEPOT_SHIP] * northern_tiles.w * northern_tiles.h); + + /* Update infrastructure counts after the tile clears earlier. + * Clearing object tiles may result in water tiles which are already accounted for in the water infrastructure total. + * See: MakeWaterKeepingClass() */ + uint new_water_infra = 0; + + for (TileIndex t : complete_area) { + /* Build water depots in water valid tiles... */ + if (!IsValidTile(t) || !HasTileWaterGround(t)) return_cmd_error(STR_ERROR_MUST_BE_BUILT_ON_WATER); + + /* ... with no bridges above... */ + if (IsBridgeAbove(t)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + + /* ... and preventing depots on rapids. */ + if (!IsTileFlat(t)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE); + + /* Keep original water class before clearing tile. */ + WaterClass wc = GetWaterClass(t); + + /* Clear the tile. */ + bool add_cost = !IsWaterTile(t); + CommandCost ret = Command::Do(flags | DC_AUTO, t); + if (ret.Failed()) return ret; + if (add_cost) cost.AddCost(ret); + + if (wc == WATER_CLASS_CANAL && !(HasTileWaterClass(t) && GetWaterClass(t) == WATER_CLASS_CANAL && IsTileOwner(t, _current_company))) new_water_infra++; + + if (flags & DC_EXEC) { + DepotPart dp = northern_tiles.Contains(t) ? DEPOT_PART_NORTH : DEPOT_PART_SOUTH; + MakeShipDepot(t, _current_company, depot->index, dp, axis, wc); + CheckForDockingTile(t); + MarkTileDirtyByTile(t); + } } if (flags & DC_EXEC) { - Depot *depot = new Depot(tile, VEH_SHIP, _current_company); - depot->build_date = TimerGameCalendar::date; - - uint new_water_infra = 2 * LOCK_DEPOT_TILE_FACTOR; - /* Update infrastructure counts after the tile clears earlier. - * Clearing object tiles may result in water tiles which are already accounted for in the water infrastructure total. - * See: MakeWaterKeepingClass() */ - if (wc1 == WATER_CLASS_CANAL && !(HasTileWaterClass(tile) && GetWaterClass(tile) == WATER_CLASS_CANAL && IsTileOwner(tile, _current_company))) new_water_infra++; - if (wc2 == WATER_CLASS_CANAL && !(HasTileWaterClass(tile2) && GetWaterClass(tile2) == WATER_CLASS_CANAL && IsTileOwner(tile2, _current_company))) new_water_infra++; - - Company::Get(_current_company)->infrastructure.water += new_water_infra; + Company::Get(_current_company)->infrastructure.water += new_water_infra + + complete_area.w * complete_area.h * LOCK_DEPOT_TILE_FACTOR; DirtyCompanyInfrastructureWindows(_current_company); - MakeShipDepot(tile, _current_company, depot->index, DEPOT_PART_NORTH, axis, wc1); - MakeShipDepot(tile2, _current_company, depot->index, DEPOT_PART_SOUTH, axis, wc2); - CheckForDockingTile(tile); - CheckForDockingTile(tile2); - MarkTileDirtyByTile(tile); - MarkTileDirtyByTile(tile2); MakeDefaultName(depot); + depot->AfterAddRemove(complete_area, true); + if (join_to == NEW_DEPOT) MakeDefaultName(depot); } return cost; @@ -275,9 +284,8 @@ static CommandCost RemoveShipDepot(TileIndex tile, DoCommandFlag flags) bool do_clear = (flags & DC_FORCE_CLEAR_TILE) != 0; if (flags & DC_EXEC) { - delete Depot::GetByTile(tile); - - Company *c = Company::GetIfValid(GetTileOwner(tile)); + Depot *depot = Depot::GetByTile(tile); + Company *c = Company::GetIfValid(depot->owner); if (c != nullptr) { c->infrastructure.water -= 2 * LOCK_DEPOT_TILE_FACTOR; if (do_clear && GetWaterClass(tile) == WATER_CLASS_CANAL) c->infrastructure.water--; @@ -286,6 +294,8 @@ static CommandCost RemoveShipDepot(TileIndex tile, DoCommandFlag flags) if (!do_clear) MakeWaterKeepingClass(tile, GetTileOwner(tile)); MakeWaterKeepingClass(tile2, GetTileOwner(tile2)); + + depot->AfterAddRemove(TileArea(tile, tile2), false); } return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_SHIP]); diff --git a/src/water_cmd.h b/src/water_cmd.h index 1c56790327..60e00d4a4c 100644 --- a/src/water_cmd.h +++ b/src/water_cmd.h @@ -13,7 +13,7 @@ #include "command_type.h" #include "water_map.h" -CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis); +CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis, bool adjacent, DepotID depot_id, TileIndex end_tile); CommandCost CmdBuildCanal(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, WaterClass wc, bool diagonal); CommandCost CmdBuildLock(DoCommandFlag flags, TileIndex tile); From 438d272cd1fee52509144da9e087316c5b6af1e2 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 26 Aug 2023 17:47:22 +0200 Subject: [PATCH 18/78] Add: Allow removing company rail depots in an area. --- src/command_type.h | 1 + src/lang/english.txt | 1 + src/rail_cmd.cpp | 26 ++++++++++++++++++++++++++ src/rail_cmd.h | 2 ++ src/rail_gui.cpp | 31 ++++++++++++++++++++----------- src/script/api/script_rail.cpp | 9 +++++++++ src/script/api/script_rail.hpp | 12 ++++++++++++ src/viewport_type.h | 1 + 8 files changed, 72 insertions(+), 11 deletions(-) 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/lang/english.txt b/src/lang/english.txt index a07296d36e..cb31b1edfb 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5123,6 +5123,7 @@ 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... diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 89554c655c..b2172560b5 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -1785,6 +1785,8 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) { + assert(IsRailDepotTile(tile)); + if (_current_company != OWNER_WATER) { CommandCost ret = CheckTileOwnership(tile); if (ret.Failed()) return ret; @@ -1822,6 +1824,30 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_TRAIN]); } +/** + * Remove train depots from an area + * @param flags operation to perform + * @param start_tile start tile of the area + * @param end_tile end tile of the area + * @return the cost of this operation or an error + */ +CommandCost CmdRemoveTrainDepot(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile) +{ + assert(IsValidTile(start_tile)); + assert(IsValidTile(end_tile)); + + CommandCost cost(EXPENSES_CONSTRUCTION); + TileArea ta(start_tile, end_tile); + for (TileIndex t : ta) { + if (!IsRailDepotTile(t)) continue; + CommandCost ret = RemoveTrainDepot(t, flags); + if (ret.Failed()) return ret; + cost.AddCost(ret); + } + + return cost; +} + static CommandCost ClearTile_Track(TileIndex tile, DoCommandFlag flags) { CommandCost cost(EXPENSES_CONSTRUCTION); diff --git a/src/rail_cmd.h b/src/rail_cmd.h index ce10c4fd77..775bfa7ed7 100644 --- a/src/rail_cmd.h +++ b/src/rail_cmd.h @@ -20,6 +20,7 @@ CommandCost CmdRemoveRailroadTrack(DoCommandFlag flags, TileIndex end_tile, Tile 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, bool adjacent, DepotID depot_id, TileIndex end_tile); +CommandCost CmdRemoveTrainDepot(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile); CommandCost CmdBuildSingleSignal(DoCommandFlag flags, TileIndex tile, Track track, SignalType sigtype, SignalVariant sigvar, bool convert_signal, bool skip_existing_signals, bool ctrl_pressed, SignalType cycle_start, SignalType cycle_stop, uint8_t num_dir_cycle, uint8_t signals_copy); CommandCost CmdRemoveSingleSignal(DoCommandFlag flags, TileIndex tile, Track track); CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_start, RailType totype, bool diagonal); @@ -31,6 +32,7 @@ DEF_CMD_TRAIT(CMD_REMOVE_RAILROAD_TRACK, CmdRemoveRailroadTrack, CMD_AUTO, DEF_CMD_TRAIT(CMD_BUILD_SINGLE_RAIL, CmdBuildSingleRail, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_REMOVE_SINGLE_RAIL, CmdRemoveSingleRail, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_BUILD_TRAIN_DEPOT, CmdBuildTrainDepot, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_REMOVE_TRAIN_DEPOT, CmdRemoveTrainDepot, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_BUILD_SINGLE_SIGNAL, CmdBuildSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_REMOVE_SINGLE_SIGNAL, CmdRemoveSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_CONVERT_RAIL, CmdConvertRail, 0, CMDT_LANDSCAPE_CONSTRUCTION) diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 271700e8c8..226691f8ec 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -320,6 +320,7 @@ void CcBuildRailTunnel(Commands, const CommandCost &result, TileIndex tile) static void ToggleRailButton_Remove(Window *w) { CloseWindowById(WC_SELECT_STATION, 0); + CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); w->ToggleWidgetLoweredState(WID_RAT_REMOVE); w->SetWidgetDirty(WID_RAT_REMOVE); _remove_button_clicked = w->IsWidgetLowered(WID_RAT_REMOVE); @@ -336,8 +337,9 @@ static bool RailToolbar_CtrlChanged(Window *w) if (w->IsWidgetDisabled(WID_RAT_REMOVE)) return false; /* allow ctrl to switch remove mode only for these widgets */ + for (WidgetID i = WID_RAT_BUILD_NS; i <= WID_RAT_BUILD_STATION; i++) { - if ((i <= WID_RAT_AUTORAIL || i >= WID_RAT_BUILD_WAYPOINT) && w->IsWidgetLowered(i)) { + if ((i <= WID_RAT_AUTORAIL || i >= WID_RAT_BUILD_DEPOT) && w->IsWidgetLowered(i)) { ToggleRailButton_Remove(w); return true; } @@ -537,6 +539,7 @@ struct BuildRailToolbarWindow : Window { case WID_RAT_BUILD_EW: case WID_RAT_BUILD_Y: case WID_RAT_AUTORAIL: + case WID_RAT_BUILD_DEPOT: case WID_RAT_BUILD_WAYPOINT: case WID_RAT_BUILD_STATION: case WID_RAT_BUILD_SIGNALS: @@ -696,7 +699,7 @@ struct BuildRailToolbarWindow : Window { CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); ViewportPlaceMethod vpm = (DiagDirToAxis(_build_depot_direction) == 0) ? VPM_X_LIMITED : VPM_Y_LIMITED; - VpStartPlaceSizing(tile, vpm, DDSP_BUILD_DEPOT); + VpStartPlaceSizing(tile, vpm, _remove_button_clicked ? DDSP_REMOVE_DEPOT : DDSP_BUILD_DEPOT); VpSetPlaceSizingLimit(_settings_game.depot.depot_spread); break; } @@ -796,16 +799,19 @@ struct BuildRailToolbarWindow : Window { } break; - case DDSP_BUILD_DEPOT: { - bool adjacent = _ctrl_pressed; + case DDSP_BUILD_DEPOT: + if (_remove_button_clicked) { + Command::Post(STR_ERROR_CAN_T_REMOVE_TRAIN_DEPOT, CcPlaySound_CONSTRUCTION_RAIL, start_tile, end_tile); + } else { + bool adjacent = _ctrl_pressed; - auto proc = [=](DepotID join_to) -> bool { - return Command::Post(STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT, CcRailDepot, start_tile, _cur_railtype, _build_depot_direction, adjacent, join_to, end_tile); - }; + auto proc = [=](DepotID join_to) -> bool { + return Command::Post(STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT, CcRailDepot, start_tile, _cur_railtype, _build_depot_direction, adjacent, join_to, end_tile); + }; - ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_TRAIN); + ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_TRAIN); + } break; - } } } } @@ -838,8 +844,11 @@ struct BuildRailToolbarWindow : Window { EventState OnCTRLStateChange() override { - /* do not toggle Remove button by Ctrl when placing station */ - if (!this->IsWidgetLowered(WID_RAT_BUILD_STATION) && !this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT) && RailToolbar_CtrlChanged(this)) return ES_HANDLED; + /* do not toggle Remove button by Ctrl when placing station or depot */ + if (!this->IsWidgetLowered(WID_RAT_BUILD_STATION) && + !this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT) && + !this->IsWidgetLowered(WID_RAT_BUILD_DEPOT) && + RailToolbar_CtrlChanged(this)) return ES_HANDLED; return ES_NOT_HANDLED; } diff --git a/src/script/api/script_rail.cpp b/src/script/api/script_rail.cpp index 5ac4a9b059..f0c79fcff9 100644 --- a/src/script/api/script_rail.cpp +++ b/src/script/api/script_rail.cpp @@ -148,6 +148,15 @@ return ScriptObject::Command::Do(tile, (::RailType)ScriptObject::GetRailType(), entrance_dir, false, INVALID_DEPOT, tile); } +/* static */ bool ScriptRail::RemoveRailDepot(TileIndex start_tile, TileIndex end_tile) +{ + EnforceCompanyModeValid(false); + EnforcePrecondition(false, ::IsValidTile(start_tile)); + EnforcePrecondition(false, ::IsValidTile(end_tile)); + + return ScriptObject::Command::Do(start_tile, end_tile); +} + /* static */ bool ScriptRail::BuildRailStation(TileIndex tile, RailTrack direction, SQInteger num_platforms, SQInteger platform_length, StationID station_id) { EnforceCompanyModeValid(false); 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/viewport_type.h b/src/viewport_type.h index a65ec9d9f7..a582e99359 100644 --- a/src/viewport_type.h +++ b/src/viewport_type.h @@ -123,6 +123,7 @@ enum ViewportDragDropSelectionProcess { 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 From 7017fcc6763a44d69d95668080c3ba53f7296f33 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 4 Mar 2023 10:27:39 +0100 Subject: [PATCH 19/78] Add: Adapt pathfinding in YAPF and NPF for depots. --- src/pathfinder/pathfinder_func.h | 32 +++++++++++++++++++++++++++ src/pathfinder/yapf/yapf_destrail.hpp | 18 ++++++++++++++- src/pathfinder/yapf/yapf_road.cpp | 13 +++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/pathfinder/pathfinder_func.h b/src/pathfinder/pathfinder_func.h index 444b100ce7..781d356458 100644 --- a/src/pathfinder/pathfinder_func.h +++ b/src/pathfinder/pathfinder_func.h @@ -12,6 +12,7 @@ #include "../tile_cmd.h" #include "../waypoint_base.h" +#include "../depot_base.h" /** * Calculates the tile of given station that is closest to a given tile @@ -47,6 +48,37 @@ inline TileIndex CalcClosestStationTile(StationID station, TileIndex tile, Stati return TileXY(x, y); } +/** + * Calculates the tile of a depot that is closest to a given tile. + * @param depot_id The depot to calculate the distance to. + * @param tile The tile from where to calculate the distance. + * @return The closest depot tile to the given tile. + */ +static inline TileIndex CalcClosestDepotTile(DepotID depot_id, TileIndex tile) +{ + assert(Depot::IsValidID(depot_id)); + const Depot *dep = Depot::Get(depot_id); + + /* If tile area is empty, use the xy tile. */ + if (dep->ta.tile == INVALID_TILE) { + assert(dep->xy != INVALID_TILE); + return dep->xy; + } + + TileIndex best_tile = INVALID_TILE; + uint best_distance = UINT_MAX; + + for (auto const &depot_tile : dep->depot_tiles) { + uint new_distance = DistanceManhattan(depot_tile, tile); + if (new_distance < best_distance) { + best_tile = depot_tile; + best_distance = new_distance; + } + } + + return best_tile; +} + /** * Wrapper around GetTileTrackStatus() and TrackStatusToTrackdirBits(), as for * single tram bits GetTileTrackStatus() returns 0. The reason for this is diff --git a/src/pathfinder/yapf/yapf_destrail.hpp b/src/pathfinder/yapf/yapf_destrail.hpp index f39a8a2c4b..f6b871ea2c 100644 --- a/src/pathfinder/yapf/yapf_destrail.hpp +++ b/src/pathfinder/yapf/yapf_destrail.hpp @@ -118,6 +118,7 @@ protected: TileIndex m_destTile; TrackdirBits m_destTrackdirs; StationID m_dest_station_id; + DepotID m_dest_depot_id; bool m_any_depot; /** to access inherited path finder */ @@ -149,14 +150,25 @@ public: break; case OT_GOTO_DEPOT: + m_dest_station_id = INVALID_STATION; + if (v->current_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) { m_any_depot = true; + m_dest_depot_id = INVALID_DEPOT; + m_destTile = v->dest_tile; + m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_RAIL, 0)); + } else { + m_dest_depot_id = v->current_order.GetDestination(); + assert(Depot::IsValidID(m_dest_depot_id)); + m_destTile = CalcClosestDepotTile(m_dest_depot_id, v->tile); + m_destTrackdirs = INVALID_TRACKDIR_BIT; } - [[fallthrough]]; + break; default: m_destTile = v->dest_tile; m_dest_station_id = INVALID_STATION; + m_dest_depot_id = INVALID_DEPOT; m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_RAIL, 0)); break; } @@ -176,6 +188,10 @@ public: return HasStationTileRail(tile) && (GetStationIndex(tile) == m_dest_station_id) && (GetRailStationTrack(tile) == TrackdirToTrack(td)); + } else if (m_dest_depot_id != INVALID_DEPOT) { + return IsRailDepotTile(tile) + && (GetDepotIndex(tile) == m_dest_depot_id) + && (GetRailDepotTrack(tile) == TrackdirToTrack(td)); } if (m_any_depot) { diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp index 209b64b52a..910751f227 100644 --- a/src/pathfinder/yapf/yapf_road.cpp +++ b/src/pathfinder/yapf/yapf_road.cpp @@ -234,7 +234,9 @@ protected: TileIndex m_destTile; TrackdirBits m_destTrackdirs; StationID m_dest_station; + DepotID m_dest_depot; StationType m_station_type; + bool m_bus; bool m_non_artic; public: @@ -252,8 +254,14 @@ public: m_destTile = CalcClosestStationTile(m_dest_station, v->tile, m_station_type); m_non_artic = !v->HasArticulatedPart(); m_destTrackdirs = INVALID_TRACKDIR_BIT; + } else if (v->current_order.IsType(OT_GOTO_DEPOT)) { + m_dest_station = INVALID_STATION; + m_dest_depot = v->current_order.GetDestination(); + m_destTile = CalcClosestDepotTile(m_dest_depot, v->tile); + m_destTrackdirs = INVALID_TRACKDIR_BIT; } else { m_dest_station = INVALID_STATION; + m_dest_depot = INVALID_DEPOT; m_destTile = v->dest_tile; m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_ROAD, GetRoadTramType(v->roadtype))); } @@ -287,6 +295,11 @@ public: (m_non_artic || IsDriveThroughStopTile(tile)); } + if (m_dest_depot != INVALID_DEPOT) { + return IsRoadDepotTile(tile) && + GetDepotIndex(tile) == m_dest_depot; + } + return tile == m_destTile && HasTrackdir(m_destTrackdirs, trackdir); } From 8d5eca7097b2c495bd1ed06fb7a653b664803ac3 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Wed, 10 May 2023 20:22:04 +0200 Subject: [PATCH 20/78] Change: Adapt some functions that located the depot with its tile. --- src/autoreplace_cmd.cpp | 4 ++ src/depot.cpp | 33 +++++++++++++++ src/depot_base.h | 3 ++ src/lang/english.txt | 1 + src/order_cmd.cpp | 2 +- src/roadveh_cmd.cpp | 4 +- src/ship_cmd.cpp | 18 ++++----- src/train.h | 2 + src/train_cmd.cpp | 90 +++++++++++++++++++++++++++++++++++------ 9 files changed, 134 insertions(+), 23 deletions(-) diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 757f18d371..026fe129cb 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -519,6 +519,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon { Vehicle *old_head = *chain; assert(old_head->IsPrimaryVehicle()); + TileIndex tile = old_head->tile; CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, (Money)0); @@ -661,6 +662,9 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon if ((flags & DC_EXEC) != 0) CheckCargoCapacity(new_head); } + assert(IsValidTile(tile)); + if (!HasCompatibleDepotTile(tile, Train::From(new_head))) cost.MakeError(STR_ERROR_UNABLE_TO_FIND_APPROPRIATE_DEPOT_TILE); + /* If we are not in DC_EXEC undo everything, i.e. rearrange old vehicles. * We do this from back to front, so that the head of the temporary vehicle chain does not change all the time. * Note: The vehicle attach callback is disabled here :) */ diff --git a/src/depot.cpp b/src/depot.cpp index 283cc0b271..bd9a3ecc1f 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -16,6 +16,7 @@ #include "vehicle_gui.h" #include "vehiclelist.h" #include "command_func.h" +#include "vehicle_base.h" #include "safeguards.h" @@ -41,6 +42,15 @@ Depot::~Depot() /* Clear the order backup. */ 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); @@ -53,6 +63,29 @@ Depot::~Depot() this->veh_type, this->owner, this->index).Pack()); } +/** + * Of all the depot parts a depot has, return the best destination for a vehicle. + * @param v The vehicle. + * @param dep The depot vehicle \a v is heading for. + * @return The closest part of depot to vehicle v. + */ +TileIndex Depot::GetBestDepotTile(Vehicle *v) const +{ + assert(this->veh_type == v->type); + TileIndex best_depot = INVALID_TILE; + uint best_distance = UINT_MAX; + + for (const auto &tile : this->depot_tiles) { + uint new_distance = DistanceManhattan(v->tile, tile); + if (new_distance < best_distance) { + best_depot = tile; + best_distance = new_distance; + } + } + + return best_depot; +} + /** * Check we can add some tiles to this depot. * @param ta The affected tile area. diff --git a/src/depot_base.h b/src/depot_base.h index 66c6dc3472..f46634cd01 100644 --- a/src/depot_base.h +++ b/src/depot_base.h @@ -20,6 +20,7 @@ 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. */ @@ -56,6 +57,8 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> { 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? diff --git a/src/lang/english.txt b/src/lang/english.txt index cb31b1edfb..8d479e87cd 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5146,6 +5146,7 @@ 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 diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 9252e3d516..203d7784f2 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -2033,7 +2033,7 @@ 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_depot = a->current_order.GetDestination(); diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 37cfc04633..86caabea5f 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -1056,7 +1056,9 @@ bool RoadVehLeaveDepot(RoadVehicle *v, bool first) if (first) { /* We are leaving a depot, but have to go to the exact same one; re-enter */ - if (v->current_order.IsType(OT_GOTO_DEPOT) && v->tile == v->dest_tile) { + if (v->current_order.IsType(OT_GOTO_DEPOT) && + IsRoadDepotTile(v->tile) && + v->current_order.GetDestination() == GetDepotIndex(v->tile)) { VehicleEnterDepot(v); return true; } diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 8577169c32..498817772d 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -187,14 +187,14 @@ static const Depot *FindClosestShipDepot(const Vehicle *v, uint max_distance) const Depot *best_depot = nullptr; uint best_dist_sq = std::numeric_limits::max(); for (const Depot *depot : Depot::Iterate()) { + if (depot->veh_type != VEH_SHIP || depot->owner != v->owner) continue; + const TileIndex tile = depot->xy; - if (IsShipDepotTile(tile) && IsTileOwner(tile, v->owner)) { - const uint dist_sq = DistanceSquare(tile, v->tile); - if (dist_sq < best_dist_sq && dist_sq <= max_distance * max_distance && - visited_patch_hashes.count(CalculateWaterRegionPatchHash(GetWaterRegionPatchInfo(tile))) > 0) { - best_dist_sq = dist_sq; - best_depot = depot; - } + const uint dist_sq = DistanceSquare(tile, v->tile); + if (dist_sq < best_dist_sq && dist_sq <= max_distance * max_distance && + visited_patch_hashes.count(CalculateWaterRegionPatchHash(GetWaterRegionPatchInfo(tile))) > 0) { + best_dist_sq = dist_sq; + best_depot = depot; } } @@ -222,7 +222,7 @@ static void CheckIfShipNeedsService(Vehicle *v) } v->current_order.MakeGoToDepot(depot->index, ODTFB_SERVICE); - v->SetDestTile(depot->xy); + v->SetDestTile(depot->GetBestDepotTile(v)); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); } @@ -962,5 +962,5 @@ ClosestDepot Ship::FindClosestDepot() const Depot *depot = FindClosestShipDepot(this, MAX_SHIP_DEPOT_SEARCH_DISTANCE); if (depot == nullptr) return ClosestDepot(); - return ClosestDepot(depot->xy, depot->index); + return ClosestDepot(depot->GetBestDepotTile(this), depot->index); } diff --git a/src/train.h b/src/train.h index bbf1e04365..be4307307c 100644 --- a/src/train.h +++ b/src/train.h @@ -353,4 +353,6 @@ protected: // These functions should not be called outside acceleration code. } }; +bool HasCompatibleDepotTile(TileIndex tile, const Train *t); + #endif /* TRAIN_H */ diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index a8fa2ea416..2546e276ca 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -38,6 +38,7 @@ #include "misc_cmd.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" +#include "depot_base.h" #include "table/strings.h" #include "table/train_sprites.h" @@ -604,6 +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. @@ -618,8 +678,9 @@ static CommandCost CmdBuildRailWagon(DoCommandFlag flags, TileIndex tile, const 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(); @@ -675,7 +736,8 @@ static CommandCost CmdBuildRailWagon(DoCommandFlag flags, TileIndex tile, const /* Try to connect the vehicle to one of free chains of wagons. */ for (Train *w : Train::Iterate()) { - if (w->tile == tile && ///< Same depot + if (!IsRailDepotTile(w->tile)) continue; + if (GetDepotIndex(w->tile) == depot_id && ///< Same depot w->IsFreeWagon() && ///< A free wagon chain w->engine_type == e->index && ///< Same type w->First() != v && ///< Don't connect to ourself @@ -694,9 +756,10 @@ static CommandCost CmdBuildRailWagon(DoCommandFlag flags, TileIndex tile, const void NormalizeTrainVehInDepot(const Train *u) { assert(u->IsEngine()); + DepotID dep_id = GetDepotIndex(u->tile); for (const Train *v : Train::Iterate()) { - if (v->IsFreeWagon() && v->tile == u->tile && - v->track == TRACK_BIT_DEPOT) { + if (v->IsFreeWagon() && v->IsInDepot() && + GetDepotIndex(v->tile) == dep_id) { if (Command::Do(DC_EXEC, v->index, u->index, true).Failed()) { break; } @@ -750,13 +813,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); @@ -826,10 +890,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) { @@ -1231,7 +1295,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 { @@ -2304,7 +2368,9 @@ static bool CheckTrainStayInDepot(Train *v) } /* We are leaving a depot, but have to go to the exact same one; re-enter. */ - if (v->current_order.IsType(OT_GOTO_DEPOT) && v->tile == v->dest_tile) { + if (v->current_order.IsType(OT_GOTO_DEPOT) && + IsRailDepotTile(v->tile) && + v->current_order.GetDestination() == GetDepotIndex(v->tile)) { /* Service when depot has no reservation. */ if (!HasDepotReservation(v->tile)) VehicleEnterDepot(v); return true; From b5e04974be46408d5d87d0c00fa1d1b53662dd72 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Thu, 1 Apr 2021 21:19:02 +0200 Subject: [PATCH 21/78] Change: Add additional rules for placing trains in depots according to railtypes. --- src/lang/english.txt | 1 + src/train_cmd.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index 8d479e87cd..d3351b75d4 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5138,6 +5138,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 diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 2546e276ca..a94fb911c0 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -1050,6 +1050,82 @@ static CommandCost CheckNewTrain(Train *original_dst, Train *dst, Train *origina return_cmd_error(STR_ERROR_TOO_MANY_VEHICLES_IN_GAME); } +/** + * Check if a train can be placed in a depot tile. + * @param train The train. + * @param tile The tile to check whether it is possible to place the train. + * @return whether it found a depot tile in which to place the train. + */ +bool CheckPlacement(const Train *train, TileIndex tile) +{ + assert(train != nullptr); + assert(IsRailDepotTile(tile)); + + RailType rt = GetRailType(tile); + for (const Train *t = train; t != nullptr; t = t->Next()) { + RailType rail_type = Engine::Get(t->engine_type)->u.rail.railtype; + if (!IsCompatibleRail(rail_type, rt)) return false; + } + + return true; +} + +/** + * Find a valid tile before placing a train in the depot. + * @param t The train to place in a rail depot tile. + * @return a compatible tile, if any, preferabily the one the first vehicle is or INVALID_TILE if none found. + */ +TileIndex LookForTileInDepot(const Train *train) +{ + assert(train != nullptr); + assert(IsRailDepotTile(train->tile)); + TileIndex best_tile = INVALID_TILE; + + /* First candidate is the original position of the train. */ + if (CheckPlacement(train, train->tile)) { + if (HasPowerOnRail(train->railtype, GetRailType(train->tile))) return train->tile; + best_tile = train->tile; + } + + /* Check all depot tiles. */ + Depot *depot = Depot::GetByTile(train->tile); + for (std::vector::iterator it = depot->depot_tiles.begin(); it != depot->depot_tiles.end(); ++it) { + if (CheckPlacement(train, *it)) { + if (HasPowerOnRail(train->railtype, GetRailType(*it))) return *it; + if (best_tile == INVALID_TILE) best_tile = *it; + } + } + + return best_tile; +} + +/** + * Find an appropriate depot tile for a train and place + * all the vehicle chain in the same depot tile. + * @param train The train to place. + */ +void PlaceOnRailDepot(Train *train) +{ + assert(train->First() == train); + + TileIndex depot_tile = LookForTileInDepot(train); + assert(depot_tile != INVALID_TILE); + + DiagDirection diag_dir = GetRailDepotDirection(depot_tile); + int x = TileX(depot_tile) * TILE_SIZE + _vehicle_initial_x_fract[diag_dir]; + int y = TileY(depot_tile) * TILE_SIZE + _vehicle_initial_y_fract[diag_dir]; + for (Train *t = train; t != nullptr; t = t->Next()) { + t->tile = depot_tile; + t->direction = DiagDirToDir(diag_dir); + t->vehstatus |= VS_HIDDEN; + t->track = TRACK_BIT_DEPOT; + t->x_pos = x; + t->y_pos = y; + t->z_pos = GetSlopePixelZ(x, y); + t->UpdatePosition(); + } +} + /** * Check whether the train parts can be attached. * @param t the train to check @@ -1060,6 +1136,8 @@ static CommandCost CheckTrainAttachment(Train *t) /* No multi-part train, no need to check. */ if (t == nullptr || t->Next() == nullptr) return CommandCost(); + if (LookForTileInDepot(t) == INVALID_TILE) return_cmd_error(STR_ERROR_INCOMPATIBLE_RAILTYPES_WITH_DEPOT); + /* The maximum length for a train. For each part we decrease this by one * and if the result is negative the train is simply too long. */ int allowed_len = _settings_game.vehicle.max_train_length * TILE_SIZE - t->gcache.cached_veh_length; @@ -1425,8 +1503,15 @@ CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID CheckCargoCapacity(dst_head); } - if (src_head != nullptr) src_head->First()->MarkDirty(); - if (dst_head != nullptr) dst_head->First()->MarkDirty(); + if (src_head != nullptr) { + PlaceOnRailDepot(src_head->First()); + src_head->First()->MarkDirty(); + } + + if (dst_head != nullptr) { + PlaceOnRailDepot(dst_head->First()); + dst_head->First()->MarkDirty(); + } /* We are undoubtedly changing something in the depot and train list. */ InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(src->tile)); From ced241ed87a26da4d681cbb0727441f5519b3294 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 26 Aug 2023 17:48:42 +0200 Subject: [PATCH 22/78] Feature: Allow vehicle replacements even if new road or rail type is not compatible. --- src/autoreplace_cmd.cpp | 17 ++++++++++++----- src/lang/english.txt | 5 +++++ src/saveload/afterload.cpp | 5 +++++ src/saveload/saveload.h | 1 + src/settings_gui.cpp | 3 +++ src/settings_table.cpp | 16 ++++++++++++++++ src/settings_type.h | 3 +++ src/table/settings/game_settings.ini | 22 ++++++++++++++++++++++ 8 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 026fe129cb..52f4588fbc 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -71,7 +71,10 @@ bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company) switch (type) { case VEH_TRAIN: { /* make sure the railtypes are compatible */ - if ((GetRailTypeInfo(e_from->u.rail.railtype)->compatible_railtypes & GetRailTypeInfo(e_to->u.rail.railtype)->compatible_railtypes) == 0) return false; + if (!_settings_game.depot.allow_no_comp_railtype_replacements && + (GetRailTypeInfo(e_from->u.rail.railtype)->compatible_railtypes & GetRailTypeInfo(e_to->u.rail.railtype)->compatible_railtypes) == 0) { + return false; + } /* make sure we do not replace wagons with engines or vice versa */ if ((e_from->u.rail.railveh_type == RAILVEH_WAGON) != (e_to->u.rail.railveh_type == RAILVEH_WAGON)) return false; @@ -79,11 +82,15 @@ bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company) } case VEH_ROAD: - /* make sure the roadtypes are compatible */ - if ((GetRoadTypeInfo(e_from->u.road.roadtype)->powered_roadtypes & GetRoadTypeInfo(e_to->u.road.roadtype)->powered_roadtypes) == ROADTYPES_NONE) return false; + if (!_settings_game.depot.allow_no_comp_roadtype_replacements) { + /* make sure the roadtypes are compatible */ + if ((GetRoadTypeInfo(e_from->u.road.roadtype)->powered_roadtypes & GetRoadTypeInfo(e_to->u.road.roadtype)->powered_roadtypes) == ROADTYPES_NONE) { + return false; + } - /* make sure that we do not replace a tram with a normal road vehicles or vice versa */ - if (HasBit(e_from->info.misc_flags, EF_ROAD_TRAM) != HasBit(e_to->info.misc_flags, EF_ROAD_TRAM)) return false; + /* make sure that we do not replace a tram with a normal road vehicles or vice versa */ + if (HasBit(e_from->info.misc_flags, EF_ROAD_TRAM) != HasBit(e_to->info.misc_flags, EF_ROAD_TRAM)) return false; + } break; case VEH_AIRCRAFT: diff --git a/src/lang/english.txt b/src/lang/english.txt index d3351b75d4..e2e0e477ad 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1464,6 +1464,7 @@ STR_CONFIG_SETTING_STOP_ON_TOWN_ROAD_HELPTEXT :Allow construct STR_CONFIG_SETTING_STOP_ON_COMPETITOR_ROAD :Allow drive-through road stops on roads owned by competitors: {STRING2} STR_CONFIG_SETTING_STOP_ON_COMPETITOR_ROAD_HELPTEXT :Allow construction of drive-through road stops on roads owned by other companies STR_CONFIG_SETTING_DYNAMIC_ENGINES_EXISTING_VEHICLES :{WHITE}Changing this setting is not possible when there are vehicles +STR_CONFIG_SETTING_REPLACEMENTS_DIFF_TYPE :{WHITE}Disabling this setting is not possible during a game STR_CONFIG_SETTING_INFRASTRUCTURE_MAINTENANCE :Infrastructure maintenance: {STRING2} STR_CONFIG_SETTING_INFRASTRUCTURE_MAINTENANCE_HELPTEXT :When enabled, infrastructure causes maintenance costs. The cost grows over-proportional with the network size, thus affecting bigger companies more than smaller ones @@ -1621,6 +1622,10 @@ STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS :Allow to join d STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS_HELPTEXT :Allow adding parts to a depot without directly touching the existing parts. Needs Ctrl+Click while placing the new parts STR_CONFIG_SETTING_DEPOT_SPREAD :Maximum depot spread: {STRING2} STR_CONFIG_SETTING_DEPOT_SPREAD_HELPTEXT :Maximum area the parts of a single depot may be spread out on. +STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_RAIL :Allow replacing rail vehicles with incompatible rail types: {STRING2} +STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_RAIL_HELPTEXT :Allow replacing rail vehicles even if they are not compatible by rail type. +STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_ROAD :Allow replacing road vehicles with incompatible road types: {STRING2} +STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_ROAD_HELPTEXT :Allow replacing road vehicles even if they are not compatible by road type. STR_CONFIG_SETTING_STATION_SPREAD :Maximum station spread: {STRING2} STR_CONFIG_SETTING_STATION_SPREAD_HELPTEXT :Maximum area the parts of a single station may be spread out on. Note that high values will slow the game diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 42e605ffaa..39aecf5a92 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -814,6 +814,11 @@ bool AfterLoadGame() _settings_game.depot.distant_join_depots = true; } + if (IsSavegameVersionBefore(SLV_ALLOW_INCOMPATIBLE_REPLACEMENTS)) { + _settings_game.depot.allow_no_comp_railtype_replacements = false; + _settings_game.depot.allow_no_comp_roadtype_replacements = false; + } + /* Load the sprites */ GfxLoadSprites(); LoadStringWidthTable(); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 9affcf9e3d..d8f10dd049 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -393,6 +393,7 @@ enum SaveLoadVersion : uint16_t { 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. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 1b3fd4a8fe..739a339a7a 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -2149,6 +2149,9 @@ static SettingsContainer &GetSettingsTree() { depots->Add(new SettingEntry("depot.depot_spread")); depots->Add(new SettingEntry("depot.distant_join_depots")); + + depots->Add(new SettingEntry("depot.allow_no_comp_railtype_replacements")); + depots->Add(new SettingEntry("depot.allow_no_comp_roadtype_replacements")); } limitations->Add(new SettingEntry("construction.command_pause_level")); diff --git a/src/settings_table.cpp b/src/settings_table.cpp index 0fd874a38c..0f262ebfe3 100644 --- a/src/settings_table.cpp +++ b/src/settings_table.cpp @@ -380,6 +380,22 @@ static void SpriteZoomMinChanged(int32_t) MarkWholeScreenDirty(); } +static bool CheckDifferentRailRoadTypesReplacements(int32_t &new_value) +{ + if (_game_mode == GM_NORMAL) { + if (new_value == 0) { + ShowErrorMessage(STR_CONFIG_SETTING_REPLACEMENTS_DIFF_TYPE, INVALID_STRING_ID, WL_ERROR); + return false; + } + } + return true; +} + +static void InvalidateReplacementWindows(int32_t) +{ + InvalidateWindowClassesData(WC_REPLACE_VEHICLE); +} + /** * Update any possible saveload window and delete any newgrf dialogue as * its widget parts might change. Reinit all windows as it allows access to the diff --git a/src/settings_type.h b/src/settings_type.h index 22b1a44bdd..6ffe3ad624 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -575,6 +575,9 @@ 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 + + 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. */ diff --git a/src/table/settings/game_settings.ini b/src/table/settings/game_settings.ini index f2b8f2d569..2a5d50f5d8 100644 --- a/src/table/settings/game_settings.ini +++ b/src/table/settings/game_settings.ini @@ -21,6 +21,8 @@ static bool CheckRoadSide(int32_t &new_value); static bool CheckDynamicEngines(int32_t &new_value); static void StationCatchmentChanged(int32_t new_value); static void MaxVehiclesChanged(int32_t new_value); +static bool CheckDifferentRailRoadTypesReplacements(int32_t &new_value); +static void InvalidateReplacementWindows(int32_t new_value); static const SettingVariant _game_settings_table[] = { [post-amble] @@ -172,6 +174,26 @@ 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 From 224b2343f0bae3bd553bfa1e37efe8e81c7ff891 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 26 Aug 2023 17:49:33 +0200 Subject: [PATCH 23/78] Codechange: Fix some CodeQL alerts. --- src/rail_cmd.cpp | 40 +++++++++++++++++----------------------- src/road_cmd.cpp | 30 ++++++++++++------------------ src/roadveh_cmd.cpp | 33 +++++++++++++++++---------------- 3 files changed, 46 insertions(+), 57 deletions(-) diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index b2172560b5..2e02d8eb24 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -1659,32 +1659,26 @@ 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)); - if (find(affected_depots.begin(), affected_depots.end(), (tile)) == affected_depots.end()) { - affected_depots.push_back(GetDepotIndex(tile)); - } + if (find(affected_depots.begin(), affected_depots.end(), (tile)) == affected_depots.end()) { + affected_depots.push_back(GetDepotIndex(tile)); } - - found_convertible_track = true; - cost.AddCost(RailConvertCost(type, totype)); - break; - - 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; diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index e6a933ce59..39fb77ee6a 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -2270,27 +2270,21 @@ static const uint8_t _roadveh_enter_depot_dir[4] = { static VehicleEnterTileStatus VehicleEnter_Road(Vehicle *v, TileIndex tile, int, int) { - 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; + 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; - } - break; - } - - default: break; + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(rv->tile)); + return VETSB_ENTERED_WORMHOLE; } + return VETSB_CONTINUE; } diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 86caabea5f..19098d1e35 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -654,29 +654,30 @@ 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->IsInDepot()) 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; From 2ff99e2377fbb8e1f896e5c20120e919a611a914 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Wed, 10 May 2023 21:51:41 +0200 Subject: [PATCH 24/78] Change: Keep removed depots in the pool for a while. (based on patch by adf, #6328, #7051) --- src/build_vehicle_gui.cpp | 22 ++++++++++++---------- src/depot.cpp | 20 +++++++++++++++++++- src/depot_base.h | 18 +++++++++++++++++- src/depot_cmd.cpp | 15 +++++++++++++++ src/depot_gui.cpp | 1 + src/landscape.cpp | 2 ++ src/order_cmd.cpp | 2 +- src/saveload/afterload.cpp | 1 + src/saveload/depot_sl.cpp | 1 + src/saveload/saveload.h | 2 ++ src/script/api/script_depotlist.cpp | 2 +- src/ship_cmd.cpp | 2 +- src/timer/timer_game_tick.h | 1 + src/viewport.cpp | 4 ++++ 14 files changed, 78 insertions(+), 15 deletions(-) diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 45a93c0c44..77afa780f4 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -1276,6 +1276,9 @@ struct BuildVehicleWindow : Window { break; case VEH_SHIP: + this->filter.railtypes = this->listview_mode ? INVALID_RAILTYPES : depot->r_types.rail_types; + break; + case VEH_AIRCRAFT: break; } @@ -1469,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); } diff --git a/src/depot.cpp b/src/depot.cpp index bd9a3ecc1f..3451bdcf80 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -61,6 +61,23 @@ Depot::~Depot() CloseWindowById(GetWindowClassForVehicleType(this->veh_type), VehicleListIdentifier(VL_DEPOT_LIST, this->veh_type, this->owner, this->index).Pack()); + + InvalidateWindowData(WC_SELECT_DEPOT, this->veh_type); +} + +/** + * 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 + */ +void Depot::Disuse() +{ + /* Mark that the depot is demolished and start the countdown. */ + this->delete_ctr = 8; } /** @@ -139,7 +156,8 @@ void Depot::AfterAddRemove(TileArea ta, bool adding) assert(!this->depot_tiles.empty()); this->xy = this->depot_tiles[0]; } else { - delete this; + assert(this->IsInUse()); + this->Disuse(); } InvalidateWindowData(WC_SELECT_DEPOT, veh_type); diff --git a/src/depot_base.h b/src/depot_base.h index f46634cd01..6f22f2428c 100644 --- a/src/depot_base.h +++ b/src/depot_base.h @@ -32,6 +32,7 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> { 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. Station *station; ///< For aircraft, station associated with this hangar. union { @@ -67,9 +68,24 @@ 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 + */ + inline bool IsInUse() const + { + return this->delete_ctr == 0; + } + + void Disuse(); + /* Check we can add some tiles to this depot. */ CommandCost BeforeAddTiles(TileArea ta); diff --git a/src/depot_cmd.cpp b/src/depot_cmd.cpp index 32cd01558b..1bbdfffe73 100644 --- a/src/depot_cmd.cpp +++ b/src/depot_cmd.cpp @@ -17,6 +17,7 @@ #include "vehiclelist.h" #include "window_func.h" #include "depot_cmd.h" +#include "timer/timer_game_tick.h" #include "table/strings.h" @@ -76,6 +77,20 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str return CommandCost(); } +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. diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp index 511099bcd1..36b7846620 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -1133,6 +1133,7 @@ static void DepotSellAllConfirmationCallback(Window *win, bool confirmed) if (confirmed) { assert(Depot::IsValidID(win->window_number)); Depot *d = Depot::Get(win->window_number); + if (!d->IsInUse()) return; Command::Post(d->xy, d->veh_type); } } 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/order_cmd.cpp b/src/order_cmd.cpp index 203d7784f2..63a5869acb 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -787,7 +787,7 @@ CommandCost CmdInsertOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID se if ((new_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) == 0) { const Depot *dp = Depot::GetIfValid(new_order.GetDestination()); - if (dp == nullptr) return CMD_ERROR; + if (dp == nullptr || !dp->IsInUse()) return CMD_ERROR; ret = CheckOwnership(dp->owner); if (ret.Failed()) return ret; diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 39aecf5a92..6aff783288 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -2844,6 +2844,7 @@ bool AfterLoadGame() /* 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; } diff --git a/src/saveload/depot_sl.cpp b/src/saveload/depot_sl.cpp index 1987605ea3..4bc5dde166 100644 --- a/src/saveload/depot_sl.cpp +++ b/src/saveload/depot_sl.cpp @@ -33,6 +33,7 @@ static const SaveLoad _depot_desc[] = { 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/saveload.h b/src/saveload/saveload.h index d8f10dd049..2976382750 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -395,6 +395,8 @@ enum SaveLoadVersion : uint16_t { 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. + SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/script/api/script_depotlist.cpp b/src/script/api/script_depotlist.cpp index 7997d399e7..0906a86da9 100644 --- a/src/script/api/script_depotlist.cpp +++ b/src/script/api/script_depotlist.cpp @@ -24,7 +24,7 @@ ScriptDepotList::ScriptDepotList(ScriptTile::TransportType transport_type) bool is_deity = ScriptCompanyMode::IsDeity(); CompanyID owner = ScriptObject::GetCompany(); for (const Depot *depot : Depot::Iterate()) { - if (depot->veh_type != (VehicleType)transport_type || + 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/ship_cmd.cpp b/src/ship_cmd.cpp index 498817772d..1695c4e153 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -187,7 +187,7 @@ static const Depot *FindClosestShipDepot(const Vehicle *v, uint max_distance) const Depot *best_depot = nullptr; uint best_dist_sq = std::numeric_limits::max(); for (const Depot *depot : Depot::Iterate()) { - if (depot->veh_type != VEH_SHIP || depot->owner != v->owner) continue; + if (depot->veh_type != VEH_SHIP || depot->owner != v->owner || !depot->IsInUse()) continue; const TileIndex tile = depot->xy; const uint dist_sq = DistanceSquare(tile, v->tile); 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/viewport.cpp b/src/viewport.cpp index 6a9da5e44d..1892962398 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -3613,6 +3613,10 @@ 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(); } } From 6bdf50ca0263b9b9adb9fae6d333431ff645a5ec Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Mon, 6 Dec 2021 14:15:51 +0100 Subject: [PATCH 25/78] Feature: Try to reuse a removed depot when placing a new one. (based on patch by adf88, #6328, #7051) --- src/depot.cpp | 30 ++++++++++++++++++++++++++++-- src/depot_base.h | 2 ++ src/depot_cmd.cpp | 24 ++++++++++++++++++++++++ src/depot_gui.cpp | 9 +++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/depot.cpp b/src/depot.cpp index 3451bdcf80..b0e750e87b 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -65,6 +65,20 @@ Depot::~Depot() InvalidateWindowData(WC_SELECT_DEPOT, this->veh_type); } +/** + * 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; +} + /** * Schedule deletion of this depot. * @@ -73,6 +87,7 @@ Depot::~Depot() * placed again later without messing vehicle orders. * * @see Depot::IsInUse + * @see Depot::Reuse */ void Depot::Disuse() { @@ -112,13 +127,19 @@ CommandCost Depot::BeforeAddTiles(TileArea ta) { assert(ta.tile != INVALID_TILE); - if (this->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)); } - if ((ta.w > _settings_game.depot.depot_spread) || (ta.h > _settings_game.depot.depot_spread)) { + /* 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(); @@ -158,8 +179,13 @@ void Depot::AfterAddRemove(TileArea ta, bool adding) } 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); } diff --git a/src/depot_base.h b/src/depot_base.h index 6f22f2428c..f6fcd04027 100644 --- a/src/depot_base.h +++ b/src/depot_base.h @@ -78,12 +78,14 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> { * 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(); /* Check we can add some tiles to this depot. */ diff --git a/src/depot_cmd.cpp b/src/depot_cmd.cpp index 1bbdfffe73..88959bf98e 100644 --- a/src/depot_cmd.cpp +++ b/src/depot_cmd.cpp @@ -77,6 +77,23 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str return CommandCost(); } +/** + * 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; @@ -128,6 +145,12 @@ CommandCost FindJoiningDepot(TileArea ta, VehicleType veh_type, DepotID &join_to } } + 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); @@ -151,6 +174,7 @@ CommandCost FindJoiningDepot(TileArea ta, VehicleType veh_type, DepotID &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); } diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp index 36b7846620..81c0109f9d 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -1249,6 +1249,15 @@ static const Depot *FindDepotsNearby(TileArea ta, VehicleType veh_type, bool dis 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; } From 63afc96d2ab070a805ec0f4c93400083799719c4 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Wed, 10 May 2023 21:52:47 +0200 Subject: [PATCH 26/78] Feature: Show depot signs of removed depots. (based on patch by adf88, #6328, #5071) --- src/depot.cpp | 13 +++++++++ src/depot_base.h | 3 +++ src/depot_cmd.cpp | 26 ++++++++++++++++++ src/depot_func.h | 1 + src/lang/english.txt | 3 +++ src/saveload/afterload.cpp | 2 ++ src/station.cpp | 4 +++ src/town_cmd.cpp | 3 +++ src/viewport.cpp | 55 ++++++++++++++++++++++++++++++++++++++ src/viewport_kdtree.h | 7 +++++ 10 files changed, 117 insertions(+) diff --git a/src/depot.cpp b/src/depot.cpp index b0e750e87b..894fa1138e 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -17,6 +17,7 @@ #include "vehiclelist.h" #include "command_func.h" #include "vehicle_base.h" +#include "viewport_kdtree.h" #include "safeguards.h" @@ -63,6 +64,10 @@ Depot::~Depot() 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(); } /** @@ -77,6 +82,10 @@ void Depot::Reuse(TileIndex xy) 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(); } /** @@ -93,6 +102,10 @@ 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)); } /** diff --git a/src/depot_base.h b/src/depot_base.h index f6fcd04027..f25d817999 100644 --- a/src/depot_base.h +++ b/src/depot_base.h @@ -11,6 +11,7 @@ #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" @@ -33,6 +34,7 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> { 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 { @@ -87,6 +89,7 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> { void Reuse(TileIndex xy); void Disuse(); + void UpdateVirtCoord(); /* Check we can add some tiles to this depot. */ CommandCost BeforeAddTiles(TileArea ta); diff --git a/src/depot_cmd.cpp b/src/depot_cmd.cpp index 88959bf98e..1364800dd8 100644 --- a/src/depot_cmd.cpp +++ b/src/depot_cmd.cpp @@ -17,6 +17,9 @@ #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" @@ -60,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); @@ -77,6 +82,27 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str 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. diff --git a/src/depot_func.h b/src/depot_func.h index f214e6d848..d1890c8728 100644 --- a/src/depot_func.h +++ b/src/depot_func.h @@ -19,6 +19,7 @@ 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 diff --git a/src/lang/english.txt b/src/lang/english.txt index e2e0e477ad..d1526effea 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5848,6 +5848,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/saveload/afterload.cpp b/src/saveload/afterload.cpp index 6aff783288..1cdc0959a7 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(); diff --git a/src/station.cpp b/src/station.cpp index 0d4198ac6c..cf07e27f92 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -755,6 +755,9 @@ void Airport::AddHangar() */ void Airport::RemoveHangar() { + if (this->hangar == nullptr) return; + + /* TODO Check this. */ RemoveOrderFromAllVehicles(OT_GOTO_DEPOT, this->hangar->index); for (Aircraft *a : Aircraft::Iterate()) { @@ -764,6 +767,7 @@ void Airport::RemoveHangar() a->current_order.MakeDummy(); } + this->hangar->Disuse(); delete this->hangar; this->hangar = nullptr; } diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index cc8364b0a1..983065c108 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" @@ -2998,6 +2999,8 @@ CommandCost CmdRenameTown(DoCommandFlag flags, TownID town_id, const std::string ClearAllStationCachedNames(); ClearAllIndustryCachedNames(); UpdateAllStationVirtCoords(); + UpdateAllDepotVirtCoords(); + RebuildViewportKdtree(); } return CommandCost(); } diff --git a/src/viewport.cpp b/src/viewport.cpp index 1892962398..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" @@ -1366,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) { @@ -1415,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(); } @@ -1441,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); @@ -2237,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) { @@ -2268,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(); } @@ -2287,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; } @@ -2310,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; @@ -2385,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()); } 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) From cf955bc24b75b6ae65b244191f157a7e1f46a938 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Fri, 2 Apr 2021 15:11:02 +0200 Subject: [PATCH 27/78] Add: Draw roadbits in road depots. --- src/road_cmd.cpp | 71 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 39fb77ee6a..81bf8ac5b2 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -1468,11 +1468,21 @@ 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: + 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; + case ROAD_TILE_DEPOT: { + DiagDirection dir = GetRoadDepotDirection(ti->tile); + road = DiagDirToRoadBits(dir); + tram = DiagDirToRoadBits(dir); + break; + } + default: NOT_REACHED(); } } else if (IsTileType(ti->tile, MP_STATION)) { if (IsAnyRoadStop(ti->tile)) { @@ -1835,17 +1845,28 @@ 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, DiagDirToRoadBits(dir)); + uint tram_offset = GetRoadSpriteOffset(SLOPE_FLAT, DiagDirToRoadBits(dir)); + + + PaletteID pal = PAL_NONE; + const DrawTileSprites *dts = &_road_depot[dir]; + DrawGroundSprite(dts->ground.sprite, 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; @@ -1858,21 +1879,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; } } @@ -1890,7 +1901,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) { @@ -1911,6 +1924,16 @@ 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); + } + if (default_gfx) { uint offset = GetRoadSpriteOffset(SLOPE_FLAT, DiagDirToRoadBits(dir)); if (rti->UsesOverlay()) { From 408732d25b03a1a52c6059ebfeda04613abce22e Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Fri, 2 Apr 2021 16:21:34 +0200 Subject: [PATCH 28/78] Feature: Road depots may have a road and tram type at the same time. --- src/road_cmd.cpp | 46 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 81bf8ac5b2..9718f12134 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -524,9 +524,20 @@ 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: { + if (!HasRoadTypeRoad(tile) || !HasRoadTypeTram(tile)) return CMD_ERROR; + if (flags & DC_EXEC) { + Company *c = Company::GetIfValid(GetTileOwner(tile)); + c->infrastructure.road[GetRoadType(tile, rtt)] -= ROAD_DEPOT_TRACKBIT_FACTOR; + DirtyCompanyInfrastructureWindows(c->index); + SetRoadType(tile, rtt, INVALID_ROADTYPE); + Depot::GetByTile(tile)->AfterAddRemove(TileArea(tile), false); + MarkTileDirtyByTile(tile); + } + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_ROAD]); + } + + default: NOT_REACHED(); } } @@ -712,7 +723,16 @@ CommandCost CmdBuildRoad(DoCommandFlag flags, TileIndex tile, RoadBits pieces, R break; case ROAD_TILE_DEPOT: - if ((GetAnyRoadBits(tile, rtt) & pieces) == pieces) return_cmd_error(STR_ERROR_ALREADY_BUILT); + if (DiagDirToRoadBits(GetRoadDepotDirection(tile)) == 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 +906,12 @@ 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); + UpdateCompanyRoadInfrastructure(rt, _current_company, ROAD_DEPOT_TRACKBIT_FACTOR); + 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); @@ -1227,14 +1252,19 @@ 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 && tt != INVALID_ROADTYPE) cost.AddCost(_price[PR_CLEAR_DEPOT_ROAD]); + if (flags & DC_EXEC) { 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; + if (rt != INVALID_ROADTYPE) c->infrastructure.road[rt] -= ROAD_DEPOT_TRACKBIT_FACTOR; + if (tt != INVALID_ROADTYPE) c->infrastructure.road[tt] -= ROAD_DEPOT_TRACKBIT_FACTOR; DirtyCompanyInfrastructureWindows(c->index); } @@ -1242,7 +1272,7 @@ static CommandCost RemoveRoadDepot(TileIndex tile, DoCommandFlag flags) depot->AfterAddRemove(TileArea(tile), false); } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_ROAD]); + return cost; } static CommandCost ClearTile_Road(TileIndex tile, DoCommandFlag flags) From 2ddb3bf2d7386b7eeaa0c206cdc8b80a2509b0eb Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 26 Aug 2023 17:50:40 +0200 Subject: [PATCH 29/78] Add: Add settings for building new types of depots. --- src/lang/english.txt | 15 +++++++++ src/saveload/afterload.cpp | 7 +++++ src/saveload/saveload.h | 2 ++ src/settings_gui.cpp | 4 +++ src/settings_table.cpp | 5 +++ src/settings_type.h | 10 ++++++ src/table/settings/game_settings.ini | 46 ++++++++++++++++++++++++++++ 7 files changed, 89 insertions(+) diff --git a/src/lang/english.txt b/src/lang/english.txt index d1526effea..a43c07b570 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1622,6 +1622,19 @@ STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS :Allow to join d 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} @@ -5134,6 +5147,8 @@ STR_ERROR_CAN_T_BUILD_TRAM_DEPOT :{WHITE}Can't bu 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_CAN_T_RENAME_DEPOT :{WHITE}Can't rename depot... STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT :{WHITE}... must be stopped inside a depot diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 1cdc0959a7..66e0798095 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -821,6 +821,13 @@ bool AfterLoadGame() _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(); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 2976382750..f498fa2839 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -397,6 +397,8 @@ enum SaveLoadVersion : uint16_t { 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. + SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 739a339a7a..9a24e44b47 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -2150,6 +2150,10 @@ static SettingsContainer &GetSettingsTree() 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")); } diff --git a/src/settings_table.cpp b/src/settings_table.cpp index 0f262ebfe3..72100e8f6b 100644 --- a/src/settings_table.cpp +++ b/src/settings_table.cpp @@ -396,6 +396,11 @@ 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 6ffe3ad624..7caa7cd30f 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -570,12 +570,22 @@ 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 }; diff --git a/src/table/settings/game_settings.ini b/src/table/settings/game_settings.ini index 2a5d50f5d8..1b43b8eff4 100644 --- a/src/table/settings/game_settings.ini +++ b/src/table/settings/game_settings.ini @@ -23,6 +23,7 @@ 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] @@ -147,6 +148,51 @@ 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 From 08f4ae4b3d1aeacf21c41601db93a0640ab410a4 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 27 Mar 2021 13:51:37 +0100 Subject: [PATCH 30/78] Add: Add and adapt some functions for extended depots. --- src/depot.cpp | 14 ++++- src/depot_map.h | 92 ++++++++++++++++++++++++++++++++ src/depot_type.h | 8 +++ src/lang/english.txt | 1 + src/pathfinder/pathfinder_func.h | 11 +++- src/vehicle.cpp | 16 ++++++ 6 files changed, 139 insertions(+), 3 deletions(-) diff --git a/src/depot.cpp b/src/depot.cpp index 894fa1138e..3a900988eb 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -112,19 +112,29 @@ void Depot::Disuse() * Of all the depot parts a depot has, return the best destination for a vehicle. * @param v The vehicle. * @param dep The depot vehicle \a v is heading for. - * @return The closest part of depot to vehicle v. + * @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); - if (new_distance < best_distance) { +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; } } diff --git a/src/depot_map.h b/src/depot_map.h index 51639a3b41..18d172fbeb 100644 --- a/src/depot_map.h +++ b/src/depot_map.h @@ -83,4 +83,96 @@ 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); +} + #endif /* DEPOT_MAP_H */ diff --git a/src/depot_type.h b/src/depot_type.h index 2fdafc00ea..9a3812799e 100644 --- a/src/depot_type.h +++ b/src/depot_type.h @@ -18,4 +18,12 @@ 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/lang/english.txt b/src/lang/english.txt index a43c07b570..db85c9e349 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -910,6 +910,7 @@ 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_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 diff --git a/src/pathfinder/pathfinder_func.h b/src/pathfinder/pathfinder_func.h index 781d356458..410776511b 100644 --- a/src/pathfinder/pathfinder_func.h +++ b/src/pathfinder/pathfinder_func.h @@ -66,13 +66,22 @@ static inline TileIndex CalcClosestDepotTile(DepotID depot_id, TileIndex tile) } 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); - if (new_distance < best_distance) { + 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; } } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index d10f8dcb0f..64876f780b 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -790,6 +790,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; From ece7f8401f5eed7ba7e5025af514fad3c77e6082 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 26 Aug 2023 17:52:41 +0200 Subject: [PATCH 31/78] Change: Add a wait counter for all vehicle types. --- src/saveload/vehicle_sl.cpp | 4 +++- src/vehicle_base.h | 25 +++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) 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/vehicle_base.h b/src/vehicle_base.h index 65fac6e103..b13e6bda8e 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -313,23 +313,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. From d5306c9e5503c9fc566ce25f2c631e78780e7ced Mon Sep 17 00:00:00 2001 From: Juanjo Date: Sat, 23 Aug 2014 18:22:18 +0200 Subject: [PATCH 32/78] Add: Add a function to check if a window has a certain widget. --- src/window_gui.h | 5 +++++ 1 file changed, 5 insertions(+) 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); From 037d8c6149baf8e404459f679d005042dfbb64ef Mon Sep 17 00:00:00 2001 From: Juanjo Date: Fri, 27 Dec 2013 18:30:36 +0000 Subject: [PATCH 33/78] Add: Add two "tracks": depot and wormhole. --- src/track_type.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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) From f713a3525d32df403e080f1d5734fe5f159ee662 Mon Sep 17 00:00:00 2001 From: Juanjo Date: Fri, 27 Dec 2013 18:50:44 +0000 Subject: [PATCH 34/78] Change: Adapt IsInDepot() for ships. --- src/ship.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ship.h b/src/ship.h index c47be05436..927dc171ef 100644 --- a/src/ship.h +++ b/src/ship.h @@ -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; From ea9ac51812c432733192f67d72153d023859af4d Mon Sep 17 00:00:00 2001 From: Juanjo Date: Fri, 27 Dec 2013 18:06:30 +0000 Subject: [PATCH 35/78] Change: Adapt GetVehicleTrackdir() for ships. --- src/ship_cmd.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 1695c4e153..4d7ce518ad 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -290,11 +290,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)); } From 01ac0e653fa466ed0d05a37053c572d19664b09f Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 26 Aug 2023 17:58:37 +0200 Subject: [PATCH 36/78] Feature: Allow building extended ship depots. --- src/dock_gui.cpp | 49 +++++++++++++++++++++++++++----- src/lang/english.txt | 6 ++-- src/script/api/script_marine.cpp | 2 +- src/water_cmd.cpp | 11 +++++-- src/water_cmd.h | 2 +- src/water_map.h | 7 +++-- src/widgets/dock_widget.h | 3 +- 7 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/dock_gui.cpp b/src/dock_gui.cpp index 2cc37266a5..2b2a369665 100644 --- a/src/dock_gui.cpp +++ b/src/dock_gui.cpp @@ -113,7 +113,12 @@ struct BuildDocksToolbarWindow : Window { { if (_game_mode == GM_NORMAL && this->IsWidgetLowered(WID_DT_STATION)) SetViewportCatchmentStation(nullptr, true); if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false); - if (_game_mode == GM_NORMAL && this->IsWidgetLowered(WID_DT_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); + + 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(); } @@ -130,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) { @@ -140,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); } @@ -167,6 +175,7 @@ struct BuildDocksToolbarWindow : Window { break; case WID_DT_DEPOT: // Build depot button + case WID_DT_EXTENDED_DEPOT: if (HandlePlacePushButton(this, widget, SPR_CURSOR_SHIP_DEPOT, HT_RECT)) { ShowBuildDocksDepotPicker(this); } @@ -209,7 +218,8 @@ struct BuildDocksToolbarWindow : Window { PlaceProc_DemolishArea(tile); break; - case WID_DT_DEPOT: { // Build depot button + case WID_DT_DEPOT: // Build depot button + 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; @@ -274,8 +284,10 @@ struct BuildDocksToolbarWindow : Window { 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, join_to, end_tile); + 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); @@ -291,7 +303,11 @@ struct BuildDocksToolbarWindow : Window { { if (_game_mode != GM_EDITOR && this->IsWidgetLowered(WID_DT_STATION)) SetViewportCatchmentStation(nullptr, true); - if (_game_mode != GM_EDITOR && this->IsWidgetLowered(WID_DT_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); + 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(); @@ -346,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. @@ -361,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), diff --git a/src/lang/english.txt b/src/lang/english.txt index db85c9e349..f1354b08e6 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2959,7 +2959,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 @@ -3202,7 +3203,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 diff --git a/src/script/api/script_marine.cpp b/src/script/api/script_marine.cpp index 5e6d60a6c6..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, false, INVALID_DEPOT, tile); + 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/water_cmd.cpp b/src/water_cmd.cpp index 7c265e81cc..235b111412 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -97,12 +97,17 @@ static void MarkCanalsAndRiversAroundDirty(TileIndex tile) * @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, bool adjacent, DepotID join_to, TileIndex end_tile) +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)); @@ -149,7 +154,7 @@ CommandCost CmdBuildShipDepot(DoCommandFlag flags, TileIndex tile, Axis axis, bo if (flags & DC_EXEC) { DepotPart dp = northern_tiles.Contains(t) ? DEPOT_PART_NORTH : DEPOT_PART_SOUTH; - MakeShipDepot(t, _current_company, depot->index, dp, axis, wc); + MakeShipDepot(t, _current_company, depot->index, extended, dp, axis, wc); CheckForDockingTile(t); MarkTileDirtyByTile(t); } @@ -984,7 +989,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(); diff --git a/src/water_cmd.h b/src/water_cmd.h index 60e00d4a4c..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, bool adjacent, DepotID depot_id, TileIndex end_tile); +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 18626e8fb4..3cfdac071a 100644 --- a/src/water_map.h +++ b/src/water_map.h @@ -24,6 +24,8 @@ enum WaterTileTypeBitLayout { WBL_TYPE_LOCK = 0x1, ///< Lock ('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. WBL_LOCK_ORIENT_BEGIN = 0, ///< Start of lock orientation bitfield. @@ -464,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); @@ -477,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/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). From e91483c588ee56acc9e70375a32600887b5a023c Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Thu, 24 Dec 2020 00:34:54 +0100 Subject: [PATCH 37/78] Change: Get a better depot when close to destination tile of depots. --- src/ship_cmd.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 4d7ce518ad..cc0e94a5e3 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -496,6 +496,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; From 9869611ad8ce7d563ec1b8f42feaab5de9ae3421 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Wed, 10 May 2023 22:02:27 +0200 Subject: [PATCH 38/78] Change: Ships enter depots when they are in the middle of the two depot tiles (not in the center of a tile). # Conflicts: # src/ship_cmd.cpp --- src/ship_cmd.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index cc0e94a5e3..cd7ff42ad9 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -773,13 +773,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()); @@ -800,6 +793,16 @@ 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)) { + 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); From 3715dcf2d756f3d93d8437d44a888e0a9eec4900 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Thu, 24 Dec 2020 00:38:26 +0100 Subject: [PATCH 39/78] Feature: Show servicing vehicles in extended depots on viewport and on vehicle window. --- src/lang/english.txt | 3 + src/script/api/script_order.cpp | 3 +- src/script/api/script_vehiclelist.cpp | 2 +- src/ship_cmd.cpp | 19 +++++ src/vehicle.cpp | 118 ++++++++++++++++++++++++-- src/vehicle_base.h | 7 ++ src/vehicle_cmd.cpp | 82 ++++++++++++++++++ src/vehicle_func.h | 1 + src/vehicle_gui.cpp | 2 + 9 files changed, 227 insertions(+), 10 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index f1354b08e6..08d2cc4f7c 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -4441,6 +4441,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} @@ -4928,6 +4929,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}% diff --git a/src/script/api/script_order.cpp b/src/script/api/script_order.cpp index 5dcd39ac4e..b11f19d03b 100644 --- a/src/script/api/script_order.cpp +++ b/src/script/api/script_order.cpp @@ -485,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_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/ship_cmd.cpp b/src/ship_cmd.cpp index cd7ff42ad9..df9b6d4b07 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -374,6 +374,25 @@ static bool CheckReverseShip(const Ship *v, Trackdir *trackdir = nullptr) return YapfShipCheckReverse(v, trackdir); } +void HandleShipEnterDepot(Ship *v) +{ + assert(IsShipDepotTile(v->tile)); + + if (IsExtendedDepot(v->tile)) { + 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; diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 64876f780b..f38c5650d7 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. * @@ -687,13 +798,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(); diff --git a/src/vehicle_base.h b/src/vehicle_base.h index b13e6bda8e..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. */ @@ -782,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 16ec099551..7147b5c84a 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -1131,3 +1131,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..eedd4c4a6d 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); diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index bb277bab34..ad00066fb7 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -3111,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)) { From 8a9a627577218a67a41de42a280aec5ea2de01a9 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Thu, 24 Dec 2020 00:45:07 +0100 Subject: [PATCH 40/78] Change: Adapt how ships enter and leave extended depots. --- src/ship_cmd.cpp | 84 ++++++++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index df9b6d4b07..cb7db048a9 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -374,6 +374,12 @@ 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)); @@ -403,45 +409,55 @@ 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; + if (!IsExtendedDepot(v->tile)) { + /* 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); + 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)); + 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; + 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; } - 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; - + v->state &= ~TRACK_BIT_DEPOT; v->cur_speed = 0; v->UpdateViewport(true, true); DepotID depot_id = GetDepotIndex(v->tile); @@ -733,6 +749,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(); @@ -816,10 +834,12 @@ static void ShipController(Ship *v) IsShipDepotTile(gp.new_tile) && GetOtherShipDepotTile(gp.new_tile) == gp.old_tile && v->current_order.GetDestination() == GetDepotIndex(gp.new_tile)) { - HandleShipEnterDepot(v); - v->UpdatePosition(); - v->UpdateViewport(true, true); - return; + if (CheckPlaceShipOnDepot(v->tile)) { + HandleShipEnterDepot(v); + v->UpdatePosition(); + v->UpdateViewport(true, true); + return; + } } const DiagDirection diagdir = DiagdirBetweenTiles(gp.old_tile, gp.new_tile); From 6f20beb49aea7a40e3c83dae1fce4e619cc7f804 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 9 Jan 2021 17:22:46 +0100 Subject: [PATCH 41/78] Codechange: Reorder code in autoreplace vehicles. --- src/autoreplace_cmd.cpp | 53 +++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 52f4588fbc..38b88b85d1 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -28,6 +28,7 @@ #include "order_cmd.h" #include "train_cmd.h" #include "vehicle_cmd.h" +#include "depot_map.h" #include "table/strings.h" @@ -777,45 +778,45 @@ 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()); - 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); - /* 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 (cost.Succeeded() && (flags & DC_EXEC) != 0) { if (free_wagon) { - cost.AddCost(ReplaceFreeUnit(&v, flags & ~DC_EXEC, ¬hing_to_do)); + ret = ReplaceFreeUnit(&v, flags, ¬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()); + ret = ReplaceChain(&v, flags, wagon_removal, ¬hing_to_do); } - /* Restart the vehicle */ - if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, false)); + assert(ret.Succeeded()); + assert(ret.GetCost() == cost.GetCost()); } - if (cost.Succeeded() && nothing_to_do) cost = CommandCost(STR_ERROR_AUTOREPLACE_NOTHING_TO_DO); + /* Restart the vehicle */ + if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, false)); + + assert(cost.Failed() || !nothing_to_do); return cost; } From 3364b1965ba5a43c7a692573662df3ccbac58b13 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Mon, 29 Mar 2021 21:03:40 +0200 Subject: [PATCH 42/78] Add: Set reservation of extended ship depots and prepare for setting it for road and train depots. --- src/autoreplace_cmd.cpp | 4 ++++ src/depot.cpp | 32 ++++++++++++++++++++++++++++++++ src/depot_map.h | 2 ++ src/ship.h | 2 +- src/ship_cmd.cpp | 14 +++++++++++++- src/vehicle.cpp | 6 +++++- 6 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 38b88b85d1..084ca8c838 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -790,6 +790,8 @@ CommandCost CmdAutoreplaceVehicle(DoCommandFlag flags, VehicleID veh_id) assert(free_wagon || v->IsStoppedInDepot()); + if (IsExtendedDepotTile(v->tile)) UpdateExtendedDepotReservation(v, false); + /* 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 */ @@ -813,6 +815,8 @@ CommandCost CmdAutoreplaceVehicle(DoCommandFlag flags, VehicleID veh_id) assert(ret.GetCost() == cost.GetCost()); } + if (IsExtendedDepotTile(v->tile)) UpdateExtendedDepotReservation(v, true); + /* Restart the vehicle */ if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, false)); diff --git a/src/depot.cpp b/src/depot.cpp index 3a900988eb..01b8d4e3b3 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -245,3 +245,35 @@ void Depot::RescanDepotTiles() 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; + + res_type = (v->vehstatus & VS_STOPPED) ? + DEPOT_RESERVATION_FULL_STOPPED_VEH : DEPOT_RESERVATION_IN_USE; + + switch (v->type) { + case VEH_ROAD: + break; + + case VEH_SHIP: + SetDepotReservation(v->tile, res_type); + break; + + case VEH_TRAIN: + break; + + case VEH_AIRCRAFT: + break; + + default: NOT_REACHED(); + } +} diff --git a/src/depot_map.h b/src/depot_map.h index 18d172fbeb..da3e1804c9 100644 --- a/src/depot_map.h +++ b/src/depot_map.h @@ -175,4 +175,6 @@ static inline void SetDepotReservation(Tile t, DepotReservation reservation, boo SB(t.m4(), 6, 2, reservation); } +void UpdateExtendedDepotReservation(Vehicle *v, bool state); + #endif /* DEPOT_MAP_H */ diff --git a/src/ship.h b/src/ship.h index 927dc171ef..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; diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index cb7db048a9..0f06765b4b 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -303,6 +303,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; @@ -385,6 +394,7 @@ 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(); @@ -416,7 +426,9 @@ static bool CheckShipLeaveDepot(Ship *v) /* Don't leave depot if no destination set */ if (v->dest_tile == 0) return true; - if (!IsExtendedDepot(v->tile)) { + if (IsExtendedDepot(v->tile)) { + SetDepotReservation(v->tile, DEPOT_RESERVATION_EMPTY); + } else { /* 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; diff --git a/src/vehicle.cpp b/src/vehicle.cpp index f38c5650d7..a787f89b25 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1198,7 +1198,11 @@ 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)){ + UpdateExtendedDepotReservation(v, true); + } /* Store the position of the effect as the vehicle pointer will become invalid later */ int x = v->x_pos; From 275c141bf465d2d41f4c03b9592a59abb945a355 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Thu, 24 Dec 2020 01:02:59 +0100 Subject: [PATCH 43/78] Change: Adapt commands for buying and replacing ships in extended depots. --- src/autoreplace_cmd.cpp | 25 ++++++++++++++++++++++++ src/lang/english.txt | 1 + src/ship_cmd.cpp | 43 ++++++++++++++++++++++++++++++----------- 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 084ca8c838..ffcaf46710 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" @@ -515,6 +516,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 @@ -712,6 +732,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); diff --git a/src/lang/english.txt b/src/lang/english.txt index 08d2cc4f7c..60cd9351f3 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5341,6 +5341,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 diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 0f06765b4b..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" @@ -957,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; @@ -969,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(); @@ -992,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; @@ -1014,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(); From afcbba8d6b16df43d1e3c5cd2bb6594c17503a2e Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Fri, 25 Dec 2020 15:06:18 +0100 Subject: [PATCH 44/78] Add: Do not try to cross an extended depot that has a ship inside. --- src/pathfinder/yapf/yapf_ship.cpp | 4 ++++ 1 file changed, 4 insertions(+) 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; From 2654cfeae79c5935bdb3a4bd982047909725dab8 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 26 Aug 2023 18:01:01 +0200 Subject: [PATCH 45/78] Add: Add the widget for rail depots. --- src/lang/english.txt | 3 ++- src/rail_gui.cpp | 45 ++++++++++++++++++++++++++++++++------- src/widgets/rail_widget.h | 31 ++++++++++++++------------- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index 60cd9351f3..bc24aac3fc 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2806,7 +2806,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 diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 226691f8ec..6001a5de03 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -337,9 +337,8 @@ static bool RailToolbar_CtrlChanged(Window *w) if (w->IsWidgetDisabled(WID_RAT_REMOVE)) return false; /* allow ctrl to switch remove mode only for these widgets */ - for (WidgetID i = WID_RAT_BUILD_NS; i <= WID_RAT_BUILD_STATION; i++) { - if ((i <= WID_RAT_AUTORAIL || i >= WID_RAT_BUILD_DEPOT) && w->IsWidgetLowered(i)) { + if ((i <= WID_RAT_AUTORAIL || i >= WID_RAT_BUILD_DEPOT) && w->HasWidget(i) && w->IsWidgetLowered(i)) { ToggleRailButton_Remove(w); return true; } @@ -459,7 +458,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->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, 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->Window::Close(); } @@ -511,7 +514,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; } @@ -540,6 +544,7 @@ struct BuildRailToolbarWindow : Window { 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: @@ -821,7 +826,10 @@ struct BuildRailToolbarWindow : Window { if (this->IsWidgetLowered(WID_RAT_BUILD_STATION)) SetViewportCatchmentStation(nullptr, true); if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) SetViewportCatchmentWaypoint(nullptr, true); - if (this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); + 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); @@ -847,7 +855,8 @@ struct BuildRailToolbarWindow : Window { /* do not toggle Remove button by Ctrl when placing station or depot */ if (!this->IsWidgetLowered(WID_RAT_BUILD_STATION) && !this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT) && - !this->IsWidgetLowered(WID_RAT_BUILD_DEPOT) && + !(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; } @@ -889,6 +898,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), @@ -911,8 +941,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), 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, }; From 8488abaf2fac9783d66264c776163936cc763c0e Mon Sep 17 00:00:00 2001 From: Juanjo Date: Tue, 23 Sep 2014 18:22:18 +0200 Subject: [PATCH 46/78] Add: Add IsStandard and IsExtendedRailDepotTile map functions. --- src/pathfinder/follow_track.hpp | 2 +- src/pathfinder/yapf/yapf_costrail.hpp | 5 ++- src/pbs.cpp | 8 ++--- src/rail_cmd.cpp | 1 + src/rail_map.h | 44 +++++++++++++++++++++++++++ src/train_cmd.cpp | 16 +++++++--- 6 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/pathfinder/follow_track.hpp b/src/pathfinder/follow_track.hpp index d384568b62..9a16bcba93 100644 --- a/src/pathfinder/follow_track.hpp +++ b/src/pathfinder/follow_track.hpp @@ -311,7 +311,7 @@ protected: 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; diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index f6217d2b24..d50bb24377 100644 --- a/src/pathfinder/yapf/yapf_costrail.hpp +++ b/src/pathfinder/yapf/yapf_costrail.hpp @@ -389,13 +389,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() && diff --git a/src/pbs.cpp b/src/pbs.cpp index 363404330c..03b5311f89 100644 --- a/src/pbs.cpp +++ b/src/pbs.cpp @@ -240,7 +240,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 +292,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); @@ -379,7 +379,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 +432,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. */ diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 2e02d8eb24..b2840174a4 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -2960,6 +2960,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(); diff --git a/src/rail_map.h b/src/rail_map.h index 03bca7e6e3..a715fc67fe 100644 --- a/src/rail_map.h +++ b/src/rail_map.h @@ -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/train_cmd.cpp b/src/train_cmd.cpp index a94fb911c0..8f8dc12eb9 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2181,7 +2181,7 @@ 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 (TryPathReserve(v, false, first_tile_okay)) { @@ -2549,7 +2549,7 @@ void FreeTrainTrackReservation(const Train *v) StationID station_id = IsRailStationTile(v->tile) ? GetStationIndex(v->tile) : INVALID_STATION; /* 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()) { @@ -3005,6 +3005,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; @@ -3823,7 +3824,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); @@ -3974,8 +3975,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; From dbd895ee6a7212ec460f53fefc0faa930090e723 Mon Sep 17 00:00:00 2001 From: Juanjo Date: Sat, 4 Jan 2014 16:21:34 +0100 Subject: [PATCH 47/78] Change: Transparent depots show all rail track. --- src/direction_func.h | 10 +++ src/rail_cmd.cpp | 157 +++++++++++++---------------------------- src/table/track_land.h | 12 ++-- 3 files changed, 64 insertions(+), 115 deletions(-) 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/rail_cmd.cpp b/src/rail_cmd.cpp index b2840174a4..cb07c49d1c 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -2117,7 +2117,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)) { @@ -2157,7 +2162,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? */ @@ -2387,7 +2403,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); @@ -2470,124 +2493,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(); 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 From 394c5050b2a075b909a8126d3973ddbe41a19f16 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 26 Aug 2023 18:18:07 +0200 Subject: [PATCH 48/78] Feature: Allow building extended train depots and fix their sprites. --- src/lang/english.txt | 5 +- src/pathfinder/yapf/yapf_rail.cpp | 2 + src/pbs.cpp | 23 +++ src/rail_cmd.cpp | 235 +++++++++++++++++++++++++----- src/rail_cmd.h | 4 +- src/rail_gui.cpp | 18 ++- src/script/api/script_rail.cpp | 2 +- 7 files changed, 244 insertions(+), 45 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index bc24aac3fc..8df4c00602 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -3174,7 +3174,8 @@ 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 @@ -5155,6 +5156,8 @@ STR_ERROR_CAN_T_BUILD_SHIP_DEPOT :{WHITE}Can't bu 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_RENAME_DEPOT :{WHITE}Can't rename depot... diff --git a/src/pathfinder/yapf/yapf_rail.cpp b/src/pathfinder/yapf/yapf_rail.cpp index 9580086799..979cfb060b 100644 --- a/src/pathfinder/yapf/yapf_rail.cpp +++ b/src/pathfinder/yapf/yapf_rail.cpp @@ -646,7 +646,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/pbs.cpp b/src/pbs.cpp index 03b5311f89..209f2c0acf 100644 --- a/src/pbs.cpp +++ b/src/pbs.cpp @@ -446,3 +446,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/rail_cmd.cpp b/src/rail_cmd.cpp index cb07c49d1c..2868d174b5 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -956,6 +956,7 @@ CommandCost CmdRemoveRailroadTrack(DoCommandFlag flags, TileIndex end_tile, Tile * @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 @@ -963,11 +964,13 @@ CommandCost CmdRemoveRailroadTrack(DoCommandFlag flags, TileIndex end_tile, Tile * @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, bool adjacent, DepotID join_to, TileIndex end_tile) +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; + 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; @@ -976,14 +979,42 @@ CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType rai 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_rotated_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); @@ -994,42 +1025,82 @@ CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType rai !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]); } - /* Check whether a depot tile exists and it needs to be rotated. */ - if (IsRailDepotTile(t) && GetDepotIndex(t) == join_to && railtype == GetRailType(t)) { - if (dir == GetRailDepotDirection(t)) continue; + 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; - ret = EnsureNoVehicleOnGround(t); - if (ret.Failed()) return ret; + 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; + } + } - num_rotated_depot_tiles++; - if (flags & DC_EXEC) { - SetRailDepotExitDirection(t, dir); + /* 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 { - cost.AddCost(Command::Do(flags, t)); - if (cost.Failed()) return cost; + /* 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; - num_new_depot_tiles++; - if (flags & DC_EXEC) { - MakeRailDepot(t, _current_company, depot->index, dir, railtype); - MarkTileDirtyByTile(t); + ret = EnsureNoVehicleOnGround(t); + if (ret.Failed()) return ret; + + 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; + +new_depot_tile: + num_new_depot_tiles++; + if (flags & DC_EXEC) { - AddSideToSignalBuffer(t, INVALID_DIAGDIR, _current_company); + 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_rotated_depot_tiles == 0) return CommandCost(); + if (num_new_depot_tiles + num_overbuilt_depot_tiles == 0) return CommandCost(); - cost.AddCost(_price[PR_BUILD_DEPOT_TRAIN] * (num_new_depot_tiles + num_rotated_depot_tiles)); - cost.AddCost(RailBuildCost(railtype) * (num_new_depot_tiles + num_rotated_depot_tiles)); + 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) { Company::Get(_current_company)->infrastructure.rail[railtype] += num_new_depot_tiles; @@ -1551,6 +1622,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. @@ -1664,11 +1785,12 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s if (flags & DC_EXEC) { /* notify YAPF about the track layout change */ YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile)); - - if (find(affected_depots.begin(), affected_depots.end(), (tile)) == affected_depots.end()) { - affected_depots.push_back(GetDepotIndex(tile)); - } } + + if (find(affected_depots.begin(), affected_depots.end(), (tile)) == affected_depots.end()) { + affected_depots.push_back(GetDepotIndex(tile)); + } + cost.AddCost(RailConvertCost(type, totype)); } else { // RAIL_TILE_NORMAL, RAIL_TILE_SIGNALS if (flags & DC_EXEC) { @@ -1760,14 +1882,18 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s } } - if (flags & DC_EXEC) { - /* Update affected depots. */ - for (auto &depot_tile : affected_depots) { - Depot *dep = Depot::Get(depot_tile); + /* 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) { v->ConsistChanged(CCF_TRACK); @@ -1777,7 +1903,7 @@ 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)); @@ -1789,6 +1915,15 @@ 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) { Depot *depot = Depot::GetByTile(tile); Company *c = Company::GetIfValid(depot->owner); @@ -1802,12 +1937,24 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) if (v != nullptr) FreeTrainTrackReservation(v); } - c->infrastructure.rail[GetRailType(tile)]--; - DirtyCompanyInfrastructureWindows(c->index); + Track track = GetRailDepotTrack(tile); + RailType rt = GetRailType(tile); + bool is_extended_depot = IsExtendedDepot(tile); DoClearSquare(tile); - AddSideToSignalBuffer(tile, dir, c->index); + 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); @@ -1815,7 +1962,8 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) 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; } /** @@ -1834,7 +1982,7 @@ CommandCost CmdRemoveTrainDepot(DoCommandFlag flags, TileIndex start_tile, TileI TileArea ta(start_tile, end_tile); for (TileIndex t : ta) { if (!IsRailDepotTile(t)) continue; - CommandCost ret = RemoveTrainDepot(t, flags); + CommandCost ret = RemoveTrainDepot(t, flags, IsExtendedDepot(t)); if (ret.Failed()) return ret; cost.AddCost(ret); } @@ -1890,7 +2038,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; @@ -2742,6 +2890,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; @@ -2839,7 +2994,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); @@ -3065,6 +3220,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 775bfa7ed7..bcd28fa2e3 100644 --- a/src/rail_cmd.h +++ b/src/rail_cmd.h @@ -19,7 +19,7 @@ CommandCost CmdBuildRailroadTrack(DoCommandFlag flags, TileIndex end_tile, TileI CommandCost CmdRemoveRailroadTrack(DoCommandFlag flags, TileIndex end_tile, TileIndex start_tile, Track track); CommandCost CmdBuildSingleRail(DoCommandFlag flags, TileIndex tile, RailType railtype, Track track, bool auto_remove_signals); CommandCost CmdRemoveSingleRail(DoCommandFlag flags, TileIndex tile, Track track); -CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir, bool adjacent, DepotID depot_id, TileIndex end_tile); +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); @@ -42,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, bool adjacent, DepotID join_to, TileIndex end_tile); +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 6001a5de03..0e801ba782 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -138,9 +138,10 @@ static const DiagDirection _place_depot_extra_dir[12] = { DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_NW, DIAGDIR_NE, }; -void CcRailDepot(Commands, const CommandCost &result, TileIndex start_tile, RailType, DiagDirection dir, bool, DepotID, TileIndex end_tile) +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, start_tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); @@ -613,7 +614,8 @@ struct BuildRailToolbarWindow : Window { break; case WID_RAT_BUILD_DEPOT: - if (HandlePlacePushButton(this, WID_RAT_BUILD_DEPOT, GetRailTypeInfo(_cur_railtype)->cursor.depot, HT_RECT)) { + case WID_RAT_BUILD_EXTENDED_DEPOT: + if (HandlePlacePushButton(this, widget, GetRailTypeInfo(_cur_railtype)->cursor.depot, HT_RECT)) { ShowBuildTrainDepotPicker(this); this->last_user_action = widget; } @@ -700,10 +702,14 @@ struct BuildRailToolbarWindow : Window { PlaceProc_DemolishArea(tile); break; - case WID_RAT_BUILD_DEPOT: { + case WID_RAT_BUILD_DEPOT: + case WID_RAT_BUILD_EXTENDED_DEPOT: { CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN); - ViewportPlaceMethod vpm = (DiagDirToAxis(_build_depot_direction) == 0) ? VPM_X_LIMITED : VPM_Y_LIMITED; + 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; @@ -805,13 +811,15 @@ 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, join_to, end_tile); + 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); diff --git a/src/script/api/script_rail.cpp b/src/script/api/script_rail.cpp index f0c79fcff9..d92010de7f 100644 --- a/src/script/api/script_rail.cpp +++ b/src/script/api/script_rail.cpp @@ -145,7 +145,7 @@ DiagDirection entrance_dir = (::TileX(tile) == ::TileX(front)) ? (::TileY(tile) < ::TileY(front) ? DIAGDIR_SE : DIAGDIR_NW) : (::TileX(tile) < ::TileX(front) ? DIAGDIR_SW : DIAGDIR_NE); - return ScriptObject::Command::Do(tile, (::RailType)ScriptObject::GetRailType(), entrance_dir, false, INVALID_DEPOT, tile); + 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) From f161a138b942d68354b373578fb2993f36c02dc8 Mon Sep 17 00:00:00 2001 From: Juanjo Date: Sat, 4 Jan 2014 16:21:24 +0100 Subject: [PATCH 49/78] Add: New depot direction picker for extended rail depots. --- src/rail_gui.cpp | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 0e801ba782..94d67ab5ce 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -73,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); @@ -616,7 +616,7 @@ struct BuildRailToolbarWindow : Window { case WID_RAT_BUILD_DEPOT: case WID_RAT_BUILD_EXTENDED_DEPOT: if (HandlePlacePushButton(this, widget, GetRailTypeInfo(_cur_railtype)->cursor.depot, HT_RECT)) { - ShowBuildTrainDepotPicker(this); + ShowBuildTrainDepotPicker(this, widget == WID_RAT_BUILD_EXTENDED_DEPOT); this->last_user_action = widget; } break; @@ -1768,9 +1768,24 @@ 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); } @@ -1852,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 { From 4854685becf67d9224377488d0d23f70d8d765fd Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Wed, 23 Dec 2020 22:32:53 +0100 Subject: [PATCH 50/78] Codechange: Move platform related code to separate files. --- src/CMakeLists.txt | 3 + src/base_station_base.h | 18 ---- src/economy.cpp | 10 +- src/newgrf_station.cpp | 1 + src/pathfinder/follow_track.hpp | 12 +-- src/pathfinder/yapf/yapf_costrail.hpp | 9 +- src/pathfinder/yapf/yapf_rail.cpp | 1 + src/pbs.cpp | 22 ---- src/platform.cpp | 138 ++++++++++++++++++++++++++ src/platform_func.h | 104 +++++++++++++++++++ src/platform_type.h | 22 ++++ src/station.cpp | 37 ------- src/station_base.h | 3 - src/station_cmd.cpp | 18 ++-- src/station_map.h | 22 ---- src/train_cmd.cpp | 13 +-- src/waypoint_base.h | 10 -- 17 files changed, 301 insertions(+), 142 deletions(-) create mode 100644 src/platform.cpp create mode 100644 src/platform_func.h create mode 100644 src/platform_type.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5f7847ff8a..3886fa5f21 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 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/economy.cpp b/src/economy.cpp index 5c0d4f7bec..9a9e54cfc2 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -56,6 +56,7 @@ #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" @@ -1621,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; @@ -1890,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/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/pathfinder/follow_track.hpp b/src/pathfinder/follow_track.hpp index 9a16bcba93..ed9e24a240 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 @@ -370,12 +371,11 @@ 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 */ + /* Entered a platform. */ + assert(HasStationTileRail(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); diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index d50bb24377..2a5bc290d6 100644 --- a/src/pathfinder/yapf/yapf_costrail.hpp +++ b/src/pathfinder/yapf/yapf_costrail.hpp @@ -590,12 +590,11 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th } } - /* Station platform-length penalty. */ + /* 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). */ + assert(HasStationTileRail(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; /* Add penalty for the inappropriate platform length. */ extra_cost += PlatformLengthPenalty(platform_length); diff --git a/src/pathfinder/yapf/yapf_rail.cpp b/src/pathfinder/yapf/yapf_rail.cpp index 979cfb060b..8d7e55267e 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" diff --git a/src/pbs.cpp b/src/pbs.cpp index 209f2c0acf..6cc6a33aa2 100644 --- a/src/pbs.cpp +++ b/src/pbs.cpp @@ -47,28 +47,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 diff --git a/src/platform.cpp b/src/platform.cpp new file mode 100644 index 0000000000..103d5a29f2 --- /dev/null +++ b/src/platform.cpp @@ -0,0 +1,138 @@ +/* + * 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" + +/** + * 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 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; + 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 platform. + * @param tile Tile to check + * @return The length of the platform in tile length. + */ +uint GetPlatformLength(TileIndex tile) +{ + switch (GetPlatformType(tile)) { + case PT_RAIL_STATION: + return GetRailStationPlatformLength(tile); + case PT_RAIL_WAYPOINT: + return 1; + 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 + * @return The length of the platform in tile length in the given direction. + */ +uint GetPlatformLength(TileIndex tile, DiagDirection dir) +{ + switch (GetPlatformType(tile)) { + case PT_RAIL_STATION: + return GetRailStationPlatformLength(tile, dir); + case PT_RAIL_WAYPOINT: + return 1; + default: NOT_REACHED(); + } +} diff --git a/src/platform_func.h b/src/platform_func.h new file mode 100644 index 0000000000..5b1275d3ef --- /dev/null +++ b/src/platform_func.h @@ -0,0 +1,104 @@ +/* + * 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 "platform_type.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); +} + +/** + * 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; + 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); + 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 + * @return whether the two tiles are compatible tiles for defining a platform + */ +static inline bool IsCompatiblePlatformTile(TileIndex test_tile, TileIndex orig_tile) +{ + switch (GetPlatformType(orig_tile)) { + case PT_RAIL_STATION: + return IsCompatibleTrainStationTile(test_tile, orig_tile); + case PT_RAIL_WAYPOINT: + return test_tile == orig_tile; + default: NOT_REACHED(); + } +} + +void SetPlatformReservation(TileIndex start, DiagDirection dir, bool b); + +uint GetPlatformLength(TileIndex tile); +uint GetPlatformLength(TileIndex tile, DiagDirection dir); + +#endif /* PLATFORM_FUNC_H */ diff --git a/src/platform_type.h b/src/platform_type.h new file mode 100644 index 0000000000..a2a48c90b3 --- /dev/null +++ b/src/platform_type.h @@ -0,0 +1,22 @@ +/* + * 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_END, + INVALID_PLATFORM_TYPE = PT_END, +}; + +#endif /* PLATFORM_TYPE_H */ diff --git a/src/station.cpp b/src/station.cpp index cf07e27f92..95475eba6a 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -269,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. diff --git a/src/station_base.h b/src/station_base.h index f5db44f23b..531339b85b 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -488,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 a0e8f8c805..9d4da2f958 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -68,6 +68,7 @@ #include "cheat_type.h" #include "road_func.h" #include "depot_base.h" +#include "platform_func.h" #include "widgets/station_widget.h" @@ -1240,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); + } /** @@ -1251,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); } /** @@ -1522,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); } } 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/train_cmd.cpp b/src/train_cmd.cpp index 8f8dc12eb9..52bc2c839b 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -39,6 +39,7 @@ #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/train_sprites.h" @@ -263,9 +264,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 */ @@ -2183,7 +2184,7 @@ void ReverseTrainDirection(Train *v) /* If we are on a depot tile facing outwards, do not treat the current tile as safe. */ 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); @@ -2526,8 +2527,8 @@ static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_ 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 */ 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. From 7ab28f604556fe213851a40c430544c66db8551e Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 20 Mar 2021 19:50:19 +0100 Subject: [PATCH 51/78] Add: Code dealing with extended rail depot platforms. --- src/depot.cpp | 33 ++++- src/pathfinder/follow_track.hpp | 47 ++++-- src/pathfinder/yapf/yapf_costrail.hpp | 4 +- src/pbs.cpp | 22 +-- src/platform.cpp | 205 ++++++++++++++++++++++++++ src/platform_func.h | 35 +++++ src/platform_type.h | 1 + src/train_cmd.cpp | 20 ++- 8 files changed, 332 insertions(+), 35 deletions(-) diff --git a/src/depot.cpp b/src/depot.cpp index 01b8d4e3b3..b498acfa90 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -18,6 +18,7 @@ #include "command_func.h" #include "vehicle_base.h" #include "viewport_kdtree.h" +#include "platform_func.h" #include "safeguards.h" @@ -212,6 +213,30 @@ void Depot::AfterAddRemove(TileArea ta, bool adding) 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... @@ -225,7 +250,7 @@ void Depot::RescanDepotTiles() for (TileIndex tile : this->ta) { if (!IsDepotTile(tile)) continue; if (GetDepotIndex(tile) != this->index) continue; - this->depot_tiles.push_back(tile); + if (IsDepotDestTile(this, tile)) this->depot_tiles.push_back(tile); switch (veh_type) { case VEH_ROAD: this->r_types.road_types |= GetPresentRoadTypes(tile); @@ -268,8 +293,12 @@ void UpdateExtendedDepotReservation(Vehicle *v, bool reserve) SetDepotReservation(v->tile, res_type); break; - case VEH_TRAIN: + 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; diff --git a/src/pathfinder/follow_track.hpp b/src/pathfinder/follow_track.hpp index ed9e24a240..2bdd6dffad 100644 --- a/src/pathfinder/follow_track.hpp +++ b/src/pathfinder/follow_track.hpp @@ -47,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; @@ -81,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; @@ -171,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; @@ -201,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 */ @@ -225,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; } @@ -369,13 +374,14 @@ protected: } } - /* special handling for rail stations - get to the end of platform */ - if (IsRailTT() && m_is_station) { + /* 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)); + 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 +396,27 @@ 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: + exitdir = GetRoadDepotDirection(m_old_tile); + 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/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index 2a5bc290d6..af7cf5de24 100644 --- a/src/pathfinder/yapf/yapf_costrail.hpp +++ b/src/pathfinder/yapf/yapf_costrail.hpp @@ -439,7 +439,7 @@ 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 @@ -592,7 +592,7 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th /* Platform-length penalty. */ if ((end_segment_reason & ESRB_STATION) != ESRB_NONE) { - assert(HasStationTileRail(n.GetLastTile())); + 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; diff --git a/src/pbs.cpp b/src/pbs.cpp index 6cc6a33aa2..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" @@ -180,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; @@ -278,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(); } } @@ -326,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; } } diff --git a/src/platform.cpp b/src/platform.cpp index 103d5a29f2..6878ad4344 100644 --- a/src/platform.cpp +++ b/src/platform.cpp @@ -11,6 +11,9 @@ #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. @@ -34,6 +37,29 @@ void SetRailStationPlatformReservation(TileIndex start, DiagDirection dir, bool } 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 @@ -49,6 +75,32 @@ void SetPlatformReservation(TileIndex start, DiagDirection dir, bool b) 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(); } } @@ -103,6 +155,57 @@ uint GetRailStationPlatformLength(TileIndex tile, DiagDirection dir) 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 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 platform. * @param tile Tile to check @@ -115,6 +218,8 @@ uint GetPlatformLength(TileIndex tile) return GetRailStationPlatformLength(tile); case PT_RAIL_WAYPOINT: return 1; + case PT_RAIL_DEPOT: + return GetRailDepotPlatformLength(tile); default: NOT_REACHED(); } } @@ -133,6 +238,106 @@ uint GetPlatformLength(TileIndex tile, DiagDirection dir) return GetRailStationPlatformLength(tile, dir); case PT_RAIL_WAYPOINT: return 1; + case PT_RAIL_DEPOT: + return GetRailDepotPlatformLength(tile, dir); 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 index 5b1275d3ef..30a2dae280 100644 --- a/src/platform_func.h +++ b/src/platform_func.h @@ -11,6 +11,7 @@ #define PLATFORM_FUNC_H #include "station_map.h" +#include "depot_map.h" #include "platform_type.h" /** @@ -35,6 +36,27 @@ static inline bool IsCompatibleTrainStationTile(TileIndex test_tile, TileIndex s 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); +} + /** * Returns the type of platform of a given tile. * @param tile Tile to check @@ -47,6 +69,9 @@ static inline PlatformType GetPlatformType(TileIndex tile) 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; default: break; } @@ -74,6 +99,8 @@ static inline bool HasPlatformReservation(TileIndex tile) case PT_RAIL_STATION: case PT_RAIL_WAYPOINT: return HasStationReservation(tile); + case PT_RAIL_DEPOT: + return HasDepotReservation(tile); default: NOT_REACHED(); } } @@ -92,13 +119,21 @@ static inline bool IsCompatiblePlatformTile(TileIndex test_tile, TileIndex orig_ 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); default: NOT_REACHED(); } } void SetPlatformReservation(TileIndex start, DiagDirection dir, bool b); +void SetPlatformReservation(TileIndex start, bool b); uint GetPlatformLength(TileIndex tile); uint GetPlatformLength(TileIndex tile, DiagDirection dir); +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 index a2a48c90b3..27765d92c7 100644 --- a/src/platform_type.h +++ b/src/platform_type.h @@ -15,6 +15,7 @@ enum PlatformType { PT_RAIL_STATION, PT_RAIL_WAYPOINT, + PT_RAIL_DEPOT, PT_END, INVALID_PLATFORM_TYPE = PT_END, }; diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 52bc2c839b..5680a31ff5 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -40,6 +40,7 @@ #include "timer/timer_game_economy.h" #include "depot_base.h" #include "platform_func.h" +#include "depot_map.h" #include "table/strings.h" #include "table/train_sprites.h" @@ -2419,7 +2420,7 @@ 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 (!u->IsInDepot() || u->tile != v->tile) return false; } /* if the train got no power, then keep it in the depot */ @@ -2523,7 +2524,7 @@ 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. */ @@ -2546,8 +2547,9 @@ 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 (IsStandardRailDepotTile(tile) && TrackdirToExitdir(td) != GetRailDepotDirection(tile)) return; @@ -2590,7 +2592,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; } @@ -2649,7 +2651,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 @@ -4358,9 +4360,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) { From fe1b0f1204e9ac3fd2f463d0c0dd173cd0cdeecb Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 2 Oct 2021 18:37:11 +0200 Subject: [PATCH 52/78] Change: Rename end segment reason for general platforms. --- src/pathfinder/yapf/yapf_costrail.hpp | 4 ++-- src/pathfinder/yapf/yapf_type.hpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index af7cf5de24..076c26389c 100644 --- a/src/pathfinder/yapf/yapf_costrail.hpp +++ b/src/pathfinder/yapf/yapf_costrail.hpp @@ -446,7 +446,7 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th * 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,7 +591,7 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th } /* Platform-length penalty. */ - if ((end_segment_reason & ESRB_STATION) != ESRB_NONE) { + 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). */ 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, From 30a62aa2b5e20b72a009ae6aa1f06e13a0343d52 Mon Sep 17 00:00:00 2001 From: Juanjo Date: Fri, 8 Aug 2014 19:20:28 +0200 Subject: [PATCH 53/78] Add: Add IsAnyDepotTileReserved. --- src/pathfinder/yapf/yapf_costrail.hpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index 076c26389c..67fdd07ebb 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; From 419ca91e59c0acadd1ce5a0cb96eb919f8957966 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Thu, 31 Dec 2020 12:30:17 +0100 Subject: [PATCH 54/78] Add: Reservation and penalties for extended depots. --- src/pathfinder/yapf/yapf_rail.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/pathfinder/yapf/yapf_rail.cpp b/src/pathfinder/yapf/yapf_rail.cpp index 8d7e55267e..d88e88110f 100644 --- a/src/pathfinder/yapf/yapf_rail.cpp +++ b/src/pathfinder/yapf/yapf_rail.cpp @@ -86,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) { @@ -95,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. */ @@ -117,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)); } From ac55c2638c46433eb1188a86df5dcad3e716c4f8 Mon Sep 17 00:00:00 2001 From: Juanjo Date: Sun, 12 Jan 2014 12:22:43 +0100 Subject: [PATCH 55/78] Add: Add vehicle entered depot platform flag. --- src/tile_cmd.h | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/tile_cmd.h b/src/tile_cmd.h index 01d116e302..867b94f222 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) From d489e22cfbe5f035a8dfd015504ebce915226def Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Thu, 24 Dec 2020 01:31:16 +0100 Subject: [PATCH 56/78] Add: Use track depot for detecting trains in depots. --- src/train.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/train.h b/src/train.h index be4307307c..677ead8d1a 100644 --- a/src/train.h +++ b/src/train.h @@ -120,7 +120,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; From 421a533fbb04e4d08b76302d2ab53a8332b040d0 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Thu, 24 Dec 2020 01:32:26 +0100 Subject: [PATCH 57/78] Change: Modify IsChainInDepot for ground vehicles. --- src/ground_vehicle.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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; From 2b259442f1fb2fb3c729aeaab4fe0c6c67cbd89a Mon Sep 17 00:00:00 2001 From: Juanjo Date: Sun, 12 Jan 2014 12:15:54 +0100 Subject: [PATCH 58/78] Add: Add a ShouldStopAtDepot function. --- src/order_base.h | 1 + src/order_cmd.cpp | 11 +++++++++++ src/train_cmd.cpp | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) 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 63a5869acb..c9000c18bc 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -2215,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/train_cmd.cpp b/src/train_cmd.cpp index 5680a31ff5..c82dc1c2d9 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2365,7 +2365,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: From b6946fb17f0a3430c94ef769c6be1283c5b54682 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 22 Apr 2023 21:18:59 +0200 Subject: [PATCH 59/78] Change: Adapt VehicleEnter_Track and handle trains entering extended depots. # Conflicts: # src/vehicle.cpp --- src/rail_cmd.cpp | 37 +++++++++++++++++++++++--- src/train.h | 1 + src/train_cmd.cpp | 68 ++++++++++++++++++++++++++++++++++++++++------- src/vehicle.cpp | 3 +-- 4 files changed, 94 insertions(+), 15 deletions(-) diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 2868d174b5..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" @@ -3076,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); @@ -3084,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. */ @@ -3110,7 +3141,7 @@ 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, GetDepotIndex(v->tile)); diff --git a/src/train.h b/src/train.h index 677ead8d1a..65fd1503c1 100644 --- a/src/train.h +++ b/src/train.h @@ -354,5 +354,6 @@ protected: // These functions should not be called outside acceleration code. }; bool HasCompatibleDepotTile(TileIndex tile, const Train *t); +bool HandleTrainEnterDepot(Train *v); #endif /* TRAIN_H */ diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index c82dc1c2d9..66ef11c055 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2411,6 +2411,31 @@ 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()) u->track |= TRACK_BIT_DEPOT; + t->force_proceed = TFP_NONE; + ClrBit(t->flags, VRF_TOGGLE_REVERSE); + 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; +} + /** * Will the train stay in the depot the next tick? * @param v %Train to check. @@ -2419,15 +2444,30 @@ 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->IsInDepot() || 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, GetDepotIndex(v->tile)); - return true; + DepotID depot_id = GetDepotIndex(v->tile); + if (IsExtendedRailDepot(v->tile)) { + for (Train *u = v; u != nullptr; u = u->Next()) u->track &= ~TRACK_BIT_DEPOT; + v->cur_speed = 0; + + v->UpdatePosition(); + v->UpdateViewport(true, true); + v->UpdateAcceleration(); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); + 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. */ @@ -2459,7 +2499,7 @@ static bool CheckTrainStayInDepot(Train *v) 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; } @@ -2489,7 +2529,7 @@ static bool CheckTrainStayInDepot(Train *v) v->UpdatePosition(); UpdateSignalsOnSegment(v->tile, INVALID_DIAGDIR, v->owner); v->UpdateAcceleration(); - InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); + InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); return false; } @@ -3442,6 +3482,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 { @@ -4098,6 +4144,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; diff --git a/src/vehicle.cpp b/src/vehicle.cpp index a787f89b25..83518b4b91 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1201,6 +1201,7 @@ void CallVehicleTicks() if (it.second) { v->vehstatus &= ~VS_STOPPED; } else if (IsExtendedDepotTile(v->tile)){ + if (v->type == VEH_TRAIN) FreeTrainTrackReservation(Train::From(v)); UpdateExtendedDepotReservation(v, true); } @@ -1683,8 +1684,6 @@ void VehicleEnterDepot(Vehicle *v) 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); From fc5445d2088bd81d218f8eec751e8aafbeb1801e Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sun, 27 Dec 2020 22:29:23 +0100 Subject: [PATCH 60/78] Add: Check length of platforms for servicing trains. --- src/lang/english.txt | 1 + src/train_cmd.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/lang/english.txt b/src/lang/english.txt index 8df4c00602..ec5ff911ae 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -911,6 +911,7 @@ STR_NEWS_VEHICLE_IS_LOST :{WHITE}{VEHICLE 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 diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 66ef11c055..e89f98c0c3 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2418,6 +2418,14 @@ bool HandleTrainEnterDepot(Train *v) 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); From 12862b5df847361e8ce4dd1522f1889ef78e14f1 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Thu, 31 Dec 2020 12:11:14 +0100 Subject: [PATCH 61/78] Change: Update signals for extended rail depots. --- src/signal.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) 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]]; From d4e89ad573aefb7653207e3f719f2cf4c1dd5dff Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Thu, 24 Dec 2020 01:38:04 +0100 Subject: [PATCH 62/78] Change: Changes related with crashed trains in extended depots. --- src/train_cmd.cpp | 43 +++++++++++++++++++++++++++++++++++++++---- src/vehicle.cpp | 18 ++++++++++++++++++ src/vehicle_func.h | 1 + 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index e89f98c0c3..f9bc2fdf9b 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -3270,6 +3270,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))); @@ -3298,6 +3299,23 @@ 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; + } + 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)) { @@ -3305,6 +3323,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, @@ -3317,6 +3352,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; @@ -3339,10 +3375,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; } @@ -3395,6 +3427,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 } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 83518b4b91..76dc88afd0 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -667,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) { diff --git a/src/vehicle_func.h b/src/vehicle_func.h index eedd4c4a6d..d92b6b1703 100644 --- a/src/vehicle_func.h +++ b/src/vehicle_func.h @@ -165,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); From 0e34a5afe2309629668d03e72db40ce66c6b2bca Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 16 Jan 2021 20:44:46 +0100 Subject: [PATCH 63/78] Add: Allow trains to reverse when leaving an extended depot. --- src/train_cmd.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index f9bc2fdf9b..579123afd2 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2444,6 +2444,19 @@ bool HandleTrainEnterDepot(Train *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. @@ -2463,6 +2476,8 @@ static bool CheckTrainStayInDepot(Train *v) v->UpdatePosition(); v->UpdateViewport(true, true); v->UpdateAcceleration(); + ProcessOrders(v); + if (CheckReverseTrain(v)) ReverseTrainDirection(v); InvalidateWindowData(WC_VEHICLE_DEPOT, depot_id); return false; } else { @@ -3112,19 +3127,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. From 0619cb633ae053b65391f47c925e69b7574d7f21 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Mon, 29 Mar 2021 17:44:44 +0200 Subject: [PATCH 64/78] Add: Base files for placing trains in extended depots. --- src/CMakeLists.txt | 2 + src/lang/english.txt | 9 ++ src/train.h | 4 + src/train_cmd.cpp | 4 +- src/train_placement.cpp | 312 ++++++++++++++++++++++++++++++++++++++++ src/train_placement.h | 58 ++++++++ 6 files changed, 386 insertions(+), 3 deletions(-) create mode 100644 src/train_placement.cpp create mode 100644 src/train_placement.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3886fa5f21..ad909c65f5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -504,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/lang/english.txt b/src/lang/english.txt index ec5ff911ae..402257d498 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5182,6 +5182,15 @@ STR_ERROR_UNABLE_TO_FIND_APPROPRIATE_DEPOT_TILE :{WHITE}Unable t 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 diff --git a/src/train.h b/src/train.h index 65fd1503c1..7edc4f5832 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, @@ -355,5 +358,6 @@ 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 579123afd2..1f1672b96a 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -41,6 +41,7 @@ #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" @@ -54,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) { diff --git a/src/train_placement.cpp b/src/train_placement.cpp new file mode 100644 index 0000000000..5b5be9f9d7 --- /dev/null +++ b/src/train_placement.cpp @@ -0,0 +1,312 @@ +/* + * 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); + + 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); + + 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 */ From dfe6be59f4eefecb1857819ee4200e33fd711f04 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 22 Apr 2023 21:29:44 +0200 Subject: [PATCH 65/78] Change: Check whether to stay in an extended rail depot or place the train and reverse if appropriate. --- src/depot_gui.cpp | 2 +- src/lang/english.txt | 1 + src/train_cmd.cpp | 192 ++++++++++++++++++++++--------------------- src/vehicle_cmd.cpp | 39 ++++++++- 4 files changed, 138 insertions(+), 96 deletions(-) diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp index 81c0109f9d..f7e745517d 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -817,7 +817,7 @@ struct DepotWindow : Window { case WID_D_STOP_ALL: case WID_D_START_ALL: { VehicleListIdentifier vli(VL_DEPOT_LIST, this->type, this->owner); - Command::Post(tile, 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; } diff --git a/src/lang/english.txt b/src/lang/english.txt index 402257d498..3566245a4c 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5160,6 +5160,7 @@ STR_ERROR_DEPOT_TYPE_NOT_AVAILABLE :{WHITE}... depo 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_RENAME_DEPOT :{WHITE}Can't rename depot... STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT :{WHITE}... must be stopped inside a depot diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 1f1672b96a..e14eb6298d 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -882,6 +882,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(); @@ -1050,82 +1054,6 @@ static CommandCost CheckNewTrain(Train *original_dst, Train *dst, Train *origina return_cmd_error(STR_ERROR_TOO_MANY_VEHICLES_IN_GAME); } -/** - * Check if a train can be placed in a depot tile. - * @param train The train. - * @param tile The tile to check whether it is possible to place the train. - * @return whether it found a depot tile in which to place the train. - */ -bool CheckPlacement(const Train *train, TileIndex tile) -{ - assert(train != nullptr); - assert(IsRailDepotTile(tile)); - - RailType rt = GetRailType(tile); - for (const Train *t = train; t != nullptr; t = t->Next()) { - RailType rail_type = Engine::Get(t->engine_type)->u.rail.railtype; - if (!IsCompatibleRail(rail_type, rt)) return false; - } - - return true; -} - -/** - * Find a valid tile before placing a train in the depot. - * @param t The train to place in a rail depot tile. - * @return a compatible tile, if any, preferabily the one the first vehicle is or INVALID_TILE if none found. - */ -TileIndex LookForTileInDepot(const Train *train) -{ - assert(train != nullptr); - assert(IsRailDepotTile(train->tile)); - TileIndex best_tile = INVALID_TILE; - - /* First candidate is the original position of the train. */ - if (CheckPlacement(train, train->tile)) { - if (HasPowerOnRail(train->railtype, GetRailType(train->tile))) return train->tile; - best_tile = train->tile; - } - - /* Check all depot tiles. */ - Depot *depot = Depot::GetByTile(train->tile); - for (std::vector::iterator it = depot->depot_tiles.begin(); it != depot->depot_tiles.end(); ++it) { - if (CheckPlacement(train, *it)) { - if (HasPowerOnRail(train->railtype, GetRailType(*it))) return *it; - if (best_tile == INVALID_TILE) best_tile = *it; - } - } - - return best_tile; -} - -/** - * Find an appropriate depot tile for a train and place - * all the vehicle chain in the same depot tile. - * @param train The train to place. - */ -void PlaceOnRailDepot(Train *train) -{ - assert(train->First() == train); - - TileIndex depot_tile = LookForTileInDepot(train); - assert(depot_tile != INVALID_TILE); - - DiagDirection diag_dir = GetRailDepotDirection(depot_tile); - int x = TileX(depot_tile) * TILE_SIZE + _vehicle_initial_x_fract[diag_dir]; - int y = TileY(depot_tile) * TILE_SIZE + _vehicle_initial_y_fract[diag_dir]; - for (Train *t = train; t != nullptr; t = t->Next()) { - t->tile = depot_tile; - t->direction = DiagDirToDir(diag_dir); - t->vehstatus |= VS_HIDDEN; - t->track = TRACK_BIT_DEPOT; - t->x_pos = x; - t->y_pos = y; - t->z_pos = GetSlopePixelZ(x, y); - t->UpdatePosition(); - } -} - /** * Check whether the train parts can be attached. * @param t the train to check @@ -1136,7 +1064,9 @@ static CommandCost CheckTrainAttachment(Train *t) /* No multi-part train, no need to check. */ if (t == nullptr || t->Next() == nullptr) return CommandCost(); - if (LookForTileInDepot(t) == INVALID_TILE) return_cmd_error(STR_ERROR_INCOMPATIBLE_RAILTYPES_WITH_DEPOT); + 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. */ @@ -1414,6 +1344,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); @@ -1426,6 +1363,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; } } @@ -1503,15 +1442,14 @@ CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID CheckCargoCapacity(dst_head); } - if (src_head != nullptr) { - PlaceOnRailDepot(src_head->First()); - src_head->First()->MarkDirty(); - } + if (src_head != nullptr) src_head->First()->MarkDirty(); + if (dst_head != nullptr) dst_head->First()->MarkDirty(); - if (dst_head != nullptr) { - PlaceOnRailDepot(dst_head->First()); - 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, GetDepotIndex(src->tile)); @@ -1520,6 +1458,8 @@ CommandCost CmdMoveRailVehicle(DoCommandFlag flags, VehicleID src_veh, VehicleID /* 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(); @@ -1544,6 +1484,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; @@ -1561,12 +1504,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); } @@ -1596,6 +1541,7 @@ 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, GetDepotIndex(v->tile)); @@ -1604,8 +1550,9 @@ CommandCost CmdSellRailWagon(DoCommandFlag flags, Vehicle *t, bool sell_chain, b /* 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; @@ -2116,7 +2063,11 @@ static bool IsWholeTrainInsideDepot(const Train *v) void ReverseTrainDirection(Train *v) { if (IsRailDepotTile(v->tile)) { - if (IsWholeTrainInsideDepot(v)) return; + 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)); } @@ -2230,7 +2181,14 @@ CommandCost CmdReverseTrainDirection(DoCommandFlag flags, VehicleID veh_id, bool ToggleBit(v->flags, VRF_REVERSE_DIRECTION); front->ConsistChanged(CCF_ARRANGE); - if (IsRailDepotTile(front->tile)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(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); @@ -2240,6 +2198,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)) { @@ -2468,15 +2429,62 @@ static bool CheckTrainStayInDepot(Train *v) DepotID depot_id = GetDepotIndex(v->tile); if (IsExtendedRailDepot(v->tile)) { - for (Train *u = v; u != nullptr; u = u->Next()) u->track &= ~TRACK_BIT_DEPOT; - v->cur_speed = 0; + /* 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); + } - v->UpdatePosition(); - v->UpdateViewport(true, true); + 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); 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()) { diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index 7147b5c84a..1a23c07a23 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -38,6 +38,8 @@ #include "train_cmd.h" #include "ship_cmd.h" #include "depot_base.h" +#include "train_placement.h" +#include "strings_func.h" #include #include @@ -523,6 +525,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) { @@ -560,6 +567,9 @@ std::tuple CmdRefitVehicle(DoCommandFla 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 }; } @@ -584,9 +594,20 @@ 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: case VEH_ROAD: @@ -638,6 +659,11 @@ 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); if (IsDepotTile(v->tile)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); @@ -677,7 +703,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(); From a0f0ece3845a5b3675256c28852c41cb11f11a86 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 26 Aug 2023 19:43:12 +0200 Subject: [PATCH 66/78] Change: Add the DC_AUTOREPLACE flag in missing places. --- src/autoreplace_cmd.cpp | 23 ++++++++++++++--------- src/train.h | 2 +- src/train_cmd.cpp | 8 +++++--- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index ffcaf46710..eaa1e1f5cd 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -373,13 +373,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; @@ -474,7 +474,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. @@ -494,7 +494,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); } } @@ -545,6 +545,8 @@ void CopyShipStatusInExtendedDepot(const Ship *old_ship, Ship *new_ship) */ 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; @@ -598,7 +600,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; @@ -617,7 +619,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 @@ -648,7 +650,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; @@ -714,7 +716,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; } } @@ -751,7 +753,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); } } } @@ -817,6 +819,9 @@ CommandCost CmdAutoreplaceVehicle(DoCommandFlag flags, VehicleID veh_id) if (IsExtendedDepotTile(v->tile)) UpdateExtendedDepotReservation(v, false); + /* 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 */ diff --git a/src/train.h b/src/train.h index 7edc4f5832..9fcd925c82 100644 --- a/src/train.h +++ b/src/train.h @@ -69,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 { diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index e14eb6298d..232be134a8 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -742,7 +742,7 @@ static CommandCost CmdBuildRailWagon(DoCommandFlag flags, TileIndex tile, const 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; } } @@ -753,14 +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->IsInDepot() && GetDepotIndex(v->tile) == dep_id) { - if (Command::Do(DC_EXEC, v->index, u->index, true).Failed()) { + if (Command::Do(flags, v->index, u->index, true).Failed()) { break; } } From c8dfc1838063dbdd8b112ec9cb1c091fc46c3d68 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 22 Apr 2023 21:40:20 +0200 Subject: [PATCH 67/78] Change: Deal with autoreplacements in extended rail depots. As the autoreplace flag is set, only lift and tryplacing in the original command for autoreplacing and not in any recursive calls to move, buy, refit and sell commands. # Conflicts: # src/autoreplace_cmd.cpp --- src/autoreplace_cmd.cpp | 34 +++++++++++++++++++++++++++++++--- src/depot_gui.cpp | 2 +- src/lang/english.txt | 1 + 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index eaa1e1f5cd..d94249e188 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -30,6 +30,8 @@ #include "train_cmd.h" #include "vehicle_cmd.h" #include "depot_map.h" +#include "train_placement.h" +#include "news_func.h" #include "table/strings.h" @@ -816,8 +818,14 @@ CommandCost CmdAutoreplaceVehicle(DoCommandFlag flags, VehicleID veh_id) if (cost.Failed()) return cost; assert(free_wagon || v->IsStoppedInDepot()); + if (flags & DC_EXEC) v->StopServicing(); - if (IsExtendedDepotTile(v->tile)) UpdateExtendedDepotReservation(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); + } /* Start autoreplacing the vehicle. */ flags |= DC_AUTOREPLACE; @@ -845,10 +853,30 @@ CommandCost CmdAutoreplaceVehicle(DoCommandFlag flags, VehicleID veh_id) assert(ret.GetCost() == cost.GetCost()); } - if (IsExtendedDepotTile(v->tile)) UpdateExtendedDepotReservation(v, true); + /* 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 (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, false)); + if (!platform_error && !was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, false)); assert(cost.Failed() || !nothing_to_do); return cost; diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp index f7e745517d..e9efecf796 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -840,7 +840,7 @@ struct DepotWindow : Window { break; case WID_D_AUTOREPLACE: - Command::Post(tile, this->type); + Command::Post(STR_ERROR_CAN_T_REPLACE_VEHICLES, tile, this->type); break; } diff --git a/src/lang/english.txt b/src/lang/english.txt index 3566245a4c..3c8b2a95c9 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5161,6 +5161,7 @@ STR_ERROR_DEPOT_EXTENDING_PLATFORMS :{WHITE}Extendin 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 From e26d14c5ef5710e5f4b86f53d913e40ca032d5cd Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sun, 21 Feb 2021 22:17:13 +0100 Subject: [PATCH 68/78] Change: Flood trains inside a train depot platform. --- src/water_cmd.cpp | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index 235b111412..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" @@ -1073,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); } /** From 328f0ab20ec6f383e056e3c0395a8a4d7fe726d6 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 2 Oct 2021 17:39:31 +0200 Subject: [PATCH 69/78] Add: Set reservation and penalties to improve pathfinding in extended depots. --- src/pathfinder/yapf/yapf_costrail.hpp | 9 +++++++++ src/train_cmd.cpp | 3 +++ src/train_placement.cpp | 2 ++ 3 files changed, 14 insertions(+) diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index 67fdd07ebb..196403aa97 100644 --- a/src/pathfinder/yapf/yapf_costrail.hpp +++ b/src/pathfinder/yapf/yapf_costrail.hpp @@ -607,6 +607,15 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th 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/train_cmd.cpp b/src/train_cmd.cpp index 232be134a8..b1022810bf 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2390,6 +2390,7 @@ bool HandleTrainEnterDepot(Train *v) 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); @@ -2481,6 +2482,7 @@ static bool CheckTrainStayInDepot(Train *v) 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. */ @@ -3315,6 +3317,7 @@ uint Train::Crash(bool flooded) 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. */ diff --git a/src/train_placement.cpp b/src/train_placement.cpp index 5b5be9f9d7..0f05da5ebc 100644 --- a/src/train_placement.cpp +++ b/src/train_placement.cpp @@ -185,6 +185,7 @@ void TrainPlacement::LiftTrain(Train *train, DoCommandFlag flags) if ((flags & DC_EXEC) == 0) return; SetPlatformReservation(train->tile, false); + UpdateExtendedDepotReservation(train, false); UpdateSignalsOnSegment(train->tile, INVALID_DIAGDIR, train->owner); } @@ -307,6 +308,7 @@ void TrainPlacement::PlaceTrain(Train *train, DoCommandFlag flags) } SetPlatformReservation(train->tile, true); + UpdateExtendedDepotReservation(train, true); UpdateSignalsOnSegment(train->tile, INVALID_DIAGDIR, train->owner); } From a64aa22b6747d1a03091a28067af741d8f6fdc8b Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Tue, 30 Mar 2021 12:11:10 +0200 Subject: [PATCH 70/78] Add: Set a darker background in depot window for trains that cannot leave a depot. --- src/depot.cpp | 1 + src/depot_gui.cpp | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/depot.cpp b/src/depot.cpp index b498acfa90..1796310cf8 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -200,6 +200,7 @@ void Depot::AfterAddRemove(TileArea ta, bool adding) 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(); diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp index e9efecf796..bfc09cf5e5 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -34,6 +34,7 @@ #include "vehicle_cmd.h" #include "core/geometry_func.hpp" #include "depot_func.h" +#include "train_placement.h" #include "widgets/depot_widget.h" @@ -262,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; @@ -411,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); @@ -723,6 +730,14 @@ struct DepotWindow : Window { 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 */ From ec5f239a9a63726c8c07da057d642c1fca86fa03 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 15 May 2021 16:32:01 +0200 Subject: [PATCH 71/78] Add: Add road depot platforms. --- src/platform.cpp | 67 +++++++++++++++++++++++++++++++++++++++++++-- src/platform_func.h | 38 +++++++++++++++++++++++-- src/platform_type.h | 1 + 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/src/platform.cpp b/src/platform.cpp index 6878ad4344..55e0871d62 100644 --- a/src/platform.cpp +++ b/src/platform.cpp @@ -183,6 +183,39 @@ uint GetRailDepotPlatformLength(TileIndex 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. @@ -206,12 +239,37 @@ uint GetRailDepotPlatformLength(TileIndex tile, DiagDirection dir) 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) +uint GetPlatformLength(TileIndex tile, RoadTramType rtt) { switch (GetPlatformType(tile)) { case PT_RAIL_STATION: @@ -220,6 +278,8 @@ uint GetPlatformLength(TileIndex tile) return 1; case PT_RAIL_DEPOT: return GetRailDepotPlatformLength(tile); + case PT_ROAD_DEPOT: + return GetRoadDepotPlatformLength(tile, rtt); default: NOT_REACHED(); } } @@ -229,9 +289,10 @@ uint GetPlatformLength(TileIndex tile) * @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) +uint GetPlatformLength(TileIndex tile, DiagDirection dir, RoadTramType rtt) { switch (GetPlatformType(tile)) { case PT_RAIL_STATION: @@ -240,6 +301,8 @@ uint GetPlatformLength(TileIndex tile, DiagDirection dir) return 1; case PT_RAIL_DEPOT: return GetRailDepotPlatformLength(tile, dir); + case PT_ROAD_DEPOT: + return GetRoadDepotPlatformLength(tile, dir, rtt); default: NOT_REACHED(); } } diff --git a/src/platform_func.h b/src/platform_func.h index 30a2dae280..baa5912874 100644 --- a/src/platform_func.h +++ b/src/platform_func.h @@ -13,6 +13,7 @@ #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. @@ -57,6 +58,31 @@ static inline bool IsCompatibleTrainDepotTile(TileIndex test_tile, TileIndex dep 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 @@ -72,6 +98,9 @@ static inline PlatformType GetPlatformType(TileIndex tile) case MP_RAILWAY: if (IsExtendedRailDepotTile(tile)) return PT_RAIL_DEPOT; break; + case MP_ROAD: + if (IsExtendedRoadDepotTile(tile)) return PT_ROAD_DEPOT; + break; default: break; } @@ -110,9 +139,10 @@ static inline bool HasPlatformReservation(TileIndex tile) * 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) +static inline bool IsCompatiblePlatformTile(TileIndex test_tile, TileIndex orig_tile, RoadTramType rtt = RTT_ROAD) { switch (GetPlatformType(orig_tile)) { case PT_RAIL_STATION: @@ -121,6 +151,8 @@ static inline bool IsCompatiblePlatformTile(TileIndex test_tile, TileIndex orig_ 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(); } } @@ -128,8 +160,8 @@ static inline bool IsCompatiblePlatformTile(TileIndex test_tile, TileIndex orig_ void SetPlatformReservation(TileIndex start, DiagDirection dir, bool b); void SetPlatformReservation(TileIndex start, bool b); -uint GetPlatformLength(TileIndex tile); -uint GetPlatformLength(TileIndex tile, DiagDirection dir); +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); diff --git a/src/platform_type.h b/src/platform_type.h index 27765d92c7..e6c543465a 100644 --- a/src/platform_type.h +++ b/src/platform_type.h @@ -16,6 +16,7 @@ enum PlatformType { PT_RAIL_STATION, PT_RAIL_WAYPOINT, PT_RAIL_DEPOT, + PT_ROAD_DEPOT, PT_END, INVALID_PLATFORM_TYPE = PT_END, }; From 718b92539a158edd94014301d3b8dfc9b35e1da3 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 26 Aug 2023 19:54:33 +0200 Subject: [PATCH 72/78] Feature: Add construction of extended road depots. --- regression/regression/result.txt | 6 +- src/depot.cpp | 35 +++- src/economy_type.h | 2 - src/lang/english.txt | 9 +- src/road_cmd.cpp | 282 ++++++++++++++++++++++++------- src/road_cmd.h | 4 +- src/road_gui.cpp | 155 ++++++++++++++--- src/road_map.cpp | 8 +- src/road_map.h | 52 ++++-- src/saveload/afterload.cpp | 9 + src/saveload/company_sl.cpp | 4 +- src/script/api/script_road.cpp | 2 +- src/town_cmd.cpp | 2 +- src/widgets/road_widget.h | 1 + 14 files changed, 458 insertions(+), 113 deletions(-) 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/depot.cpp b/src/depot.cpp index 1796310cf8..08eb0af1f1 100644 --- a/src/depot.cpp +++ b/src/depot.cpp @@ -283,12 +283,41 @@ void UpdateExtendedDepotReservation(Vehicle *v, bool reserve) assert(IsExtendedDepotTile(v->tile)); DepotReservation res_type = DEPOT_RESERVATION_EMPTY; - res_type = (v->vehstatus & VS_STOPPED) ? - DEPOT_RESERVATION_FULL_STOPPED_VEH : DEPOT_RESERVATION_IN_USE; + if (reserve) { + res_type = (v->vehstatus & VS_STOPPED) ? + DEPOT_RESERVATION_FULL_STOPPED_VEH : DEPOT_RESERVATION_IN_USE; + } switch (v->type) { - case VEH_ROAD: + 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); 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/lang/english.txt b/src/lang/english.txt index 3c8b2a95c9..5c7350fc0a 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2919,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 @@ -3181,7 +3183,8 @@ STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT_EXTENDED :Extended railwa 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 diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 9718f12134..428554d8f6 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -313,6 +313,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. @@ -525,16 +541,29 @@ static CommandCost RemoveRoad(TileIndex tile, DoCommandFlag flags, RoadBits piec } case ROAD_TILE_DEPOT: { - if (!HasRoadTypeRoad(tile) || !HasRoadTypeTram(tile)) return CMD_ERROR; - if (flags & DC_EXEC) { + /* 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)] -= ROAD_DEPOT_TRACKBIT_FACTOR; + c->infrastructure.road[GetRoadType(tile, rtt)] -= num_removed_bits; DirtyCompanyInfrastructureWindows(c->index); - SetRoadType(tile, rtt, INVALID_ROADTYPE); - Depot::GetByTile(tile)->AfterAddRemove(TileArea(tile), false); + + 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 CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_ROAD]); + return cost; } default: NOT_REACHED(); @@ -722,8 +751,24 @@ 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 (DiagDirToRoadBits(GetRoadDepotDirection(tile)) == pieces) { + 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); @@ -732,6 +777,7 @@ CommandCost CmdBuildRoad(DoCommandFlag flags, TileIndex tile, RoadBits pieces, R cost.AddCost(_price[PR_BUILD_DEPOT_ROAD]); break; } + } goto do_clear; @@ -908,7 +954,13 @@ do_clear:; RoadTileType rttype = GetRoadTileType(tile); if (rttype == ROAD_TILE_DEPOT) { SetRoadType(tile, rtt, rt); - UpdateCompanyRoadInfrastructure(rt, _current_company, ROAD_DEPOT_TRACKBIT_FACTOR); + 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) { @@ -1165,8 +1217,11 @@ 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 @@ -1174,12 +1229,36 @@ std::tuple CmdRemoveLongRoad(DoCommandFlag flags, TileIndex * @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, bool adjacent, DepotID join_to, TileIndex end_tile) +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(ta.w == 1 || ta.h == 1); + 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; @@ -1187,58 +1266,135 @@ CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, if (ret.Failed()) return ret; uint8_t num_new_depot_tiles = 0; - uint8_t num_rotated_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(t); + 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]); } - /* Check whether a depot tile exists and it needs to be rotated. */ - if (IsRoadDepotTile(t) && - GetDepotIndex(t) == join_to && - (HasRoadTypeTram(t) ? rt == GetRoadTypeTram(t) : rt == GetRoadTypeRoad(t))) { - if (dir == GetRoadDepotDirection(t)) continue; + /* 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); + } - ret = EnsureNoVehicleOnGround(t); - 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; - num_rotated_depot_tiles++; - if (flags & DC_EXEC) { - SetRoadDepotExitDirection(t, dir); - MarkTileDirtyByTile(t); + /* 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); + } + + /* 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; + } } + } + 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 { - cost.AddCost(Command::Do(flags, t)); - if (cost.Failed()) return cost; - + rb = DiagDirToRoadBits(orig_dir); + num_new_pieces += 1; num_new_depot_tiles++; - if (flags & DC_EXEC) { - MakeRoadDepot(t, _current_company, depot->index, dir, rt); - MarkTileDirtyByTile(t); + } + + 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_rotated_depot_tiles == 0) return CommandCost(); + 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) { - /* A road depot has two road bits. */ - UpdateCompanyRoadInfrastructure(rt, _current_company, num_new_depot_tiles * ROAD_DEPOT_TRACKBIT_FACTOR); + 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] * (num_new_depot_tiles + num_rotated_depot_tiles)); return cost; } @@ -1255,16 +1411,16 @@ static CommandCost RemoveRoadDepot(TileIndex tile, DoCommandFlag flags) CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_ROAD]); RoadType rt = GetRoadTypeRoad(tile); RoadType tt = GetRoadTypeTram(tile); - if (rt != INVALID_ROADTYPE && tt != INVALID_ROADTYPE) cost.AddCost(_price[PR_CLEAR_DEPOT_ROAD]); + 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) { 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) c->infrastructure.road[rt] -= ROAD_DEPOT_TRACKBIT_FACTOR; - if (tt != INVALID_ROADTYPE) c->infrastructure.road[tt] -= 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); } @@ -1500,18 +1656,13 @@ void DrawRoadCatenary(const TileInfo *ti) if (IsTileType(ti->tile, MP_ROAD)) { 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; - case ROAD_TILE_DEPOT: { - DiagDirection dir = GetRoadDepotDirection(ti->tile); - road = DiagDirToRoadBits(dir); - tram = DiagDirToRoadBits(dir); - break; - } default: NOT_REACHED(); } } else if (IsTileType(ti->tile, MP_STATION)) { @@ -1883,13 +2034,15 @@ static void DrawTile_Road(TileInfo *ti) const RoadTypeInfo *main_rti = tram_rti != nullptr ? tram_rti : road_rti; DiagDirection dir = GetRoadDepotDirection(ti->tile); - uint road_offset = GetRoadSpriteOffset(SLOPE_FLAT, DiagDirToRoadBits(dir)); - uint tram_offset = GetRoadSpriteOffset(SLOPE_FLAT, DiagDirToRoadBits(dir)); + 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]; - DrawGroundSprite(dts->ground.sprite, pal); + 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); @@ -1962,6 +2115,8 @@ void DrawRoadDepotSprite(int x, int y, DiagDirection dir, RoadType rt) } 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) { @@ -2220,11 +2375,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; } } @@ -2281,7 +2436,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; @@ -2349,14 +2504,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); } @@ -2405,7 +2560,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: { @@ -2582,8 +2740,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; diff --git a/src/road_cmd.h b/src/road_cmd.h index 2ae7e170e6..b7a54c5c4a 100644 --- a/src/road_cmd.h +++ b/src/road_cmd.h @@ -23,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, bool adjacent, DepotID join_to, TileIndex end_tile); +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) @@ -34,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 start_tile, RoadType rt, DiagDirection dir, bool adjacent, DepotID join_to, TileIndex end_tile); +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 53a71d0d2c..4b99c39f84 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -52,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; @@ -171,16 +171,45 @@ void ConnectRoadToStructure(TileIndex tile, DiagDirection direction) } } -void CcRoadDepot(Commands, const CommandCost &result, TileIndex start_tile, RoadType, DiagDirection dir, bool, DepotID, TileIndex end_tile) +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, start_tile); if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); + 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) { - ConnectRoadToStructure(tile, dir); + if (build_start && !ta.Contains(tile + TileOffsByDiagDir(dir))) { + ConnectRoadToStructure(tile, dir); + } + if (build_end && !ta.Contains(tile + TileOffsByDiagDir(ReverseDiagDir(dir)))) { + ConnectRoadToStructure(tile, ReverseDiagDir(dir)); + } } } @@ -373,7 +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->IsWidgetLowered(WID_ROT_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, 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->Window::Close(); } @@ -390,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); @@ -403,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); @@ -433,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; @@ -544,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; @@ -642,11 +682,19 @@ struct BuildRoadToolbarWindow : Window { break; case WID_ROT_DEPOT: + 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); - VpStartPlaceSizing(tile, (DiagDirToAxis(_road_depot_orientation) == 0) ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_DEPOT); + 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); @@ -681,7 +729,11 @@ struct BuildRoadToolbarWindow : Window { { if (_game_mode != GM_EDITOR && (this->IsWidgetLowered(WID_ROT_BUS_STATION) || this->IsWidgetLowered(WID_ROT_TRUCK_STATION))) SetViewportCatchmentStation(nullptr, true); - if (_game_mode == GM_NORMAL && this->IsWidgetLowered(WID_ROT_DEPOT)) SetViewportHighlightDepot(INVALID_DEPOT, true); + 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); @@ -730,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: @@ -817,11 +876,14 @@ struct BuildRoadToolbarWindow : Window { break; case DDSP_BUILD_DEPOT: { - bool adjacent = _ctrl_pressed; 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, join_to, end_tile); + 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); @@ -923,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), @@ -938,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), @@ -983,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), @@ -1114,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; } } @@ -1209,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/saveload/afterload.cpp b/src/saveload/afterload.cpp index 66e0798095..6afc3b3861 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -2876,6 +2876,15 @@ bool AfterLoadGame() 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 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/script/api/script_road.cpp b/src/script/api/script_road.cpp index 9b29e2d4b9..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, false, INVALID_DEPOT, tile); + 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/town_cmd.cpp b/src/town_cmd.cpp index 983065c108..0881cd41c9 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -1248,7 +1248,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. 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. From c270cebb596391fb82727feba777227d1c8d6512 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 2 Oct 2021 17:41:10 +0200 Subject: [PATCH 73/78] Change: Adapt pathfinders for extended road depots. --- src/pathfinder/follow_track.hpp | 27 ++++++++++++++++----------- src/pathfinder/yapf/yapf_road.cpp | 17 +++++++++++++++-- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/pathfinder/follow_track.hpp b/src/pathfinder/follow_track.hpp index 2bdd6dffad..33439d9ae8 100644 --- a/src/pathfinder/follow_track.hpp +++ b/src/pathfinder/follow_track.hpp @@ -101,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; @@ -272,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; } @@ -306,16 +308,17 @@ 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() && IsStandardRailDepotTile(m_new_tile)) { DiagDirection exitdir = GetRailDepotDirection(m_new_tile); @@ -404,9 +407,11 @@ protected: if (IsExtendedRailDepot(m_old_tile)) return false; exitdir = GetRailDepotDirection(m_old_tile); break; - case TRANSPORT_ROAD: - exitdir = GetRoadDepotDirection(m_old_tile); + 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(); } diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp index 910751f227..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) From d9b182df78de0e47d56c51cfd4247774f2e9a20e Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 13 Nov 2021 19:02:29 +0100 Subject: [PATCH 74/78] Codechange: Always return the first vehicle when looking for close road vehicles. --- src/roadveh_cmd.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 19098d1e35..0b26a40df8 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -714,6 +714,8 @@ 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(); + return RoadVehicle::From(rvf.best); } @@ -1202,7 +1204,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; } } @@ -1320,7 +1323,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); @@ -1436,7 +1440,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); @@ -1486,7 +1491,7 @@ again: RoadVehicle *u = RoadVehFindCloseTo(v, x, y, new_dir); if (u != nullptr) { - u = u->First(); + 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; From 861de832f6bef140f84f7f7c36cceb59b81464d5 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 13 Nov 2021 19:15:47 +0100 Subject: [PATCH 75/78] Add: Hide stopped road vehicles in extended depots that block the path of another vehicle. --- src/roadveh_cmd.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 0b26a40df8..48ca4b08f3 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -683,6 +683,27 @@ static Vehicle *EnumCheckRoadVehClose(Vehicle *v, void *data) 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; @@ -716,6 +737,15 @@ static RoadVehicle *RoadVehFindCloseTo(RoadVehicle *v, int x, int y, Direction d 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); } From 158c46fcd64cc9536f491ba23f6d243829430dba Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Sat, 26 Aug 2023 19:55:15 +0200 Subject: [PATCH 76/78] Change: Buying and controlling road vehicles in extended road depots. --- src/lang/english.txt | 1 + src/road_cmd.cpp | 53 +++++-- src/roadveh_cmd.cpp | 340 ++++++++++++++++++++++++++++++++----------- src/vehicle_cmd.cpp | 12 ++ 4 files changed, 309 insertions(+), 97 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index 5c7350fc0a..4abac89abe 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5378,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... diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 428554d8f6..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" @@ -2476,21 +2477,51 @@ 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) { 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; + 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; + } + } - InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(rv->tile)); - return VETSB_ENTERED_WORMHOLE; + /* 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; diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 48ca4b08f3..471485fedd 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -254,11 +254,12 @@ void RoadVehUpdateCache(RoadVehicle *v, bool same_length) /** * 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 flags Flags of the command. - * @return CommandCost() or an error message if the depot is not appropriate. + * @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, const Engine *e, DoCommandFlag flags) +CommandCost FindDepotTileForPlacingEngine(TileIndex &tile, bool &is_exit_facing_south, const Engine *e, bool already_built) { assert(IsRoadDepotTile(tile)); @@ -269,23 +270,43 @@ CommandCost FindDepotTileForPlacingEngine(TileIndex &tile, const Engine *e, DoCo const RoadTypeInfo *rti = GetRoadTypeInfo(rt); if ((dep->r_types.road_types & rti->powered_roadtypes) == 0) return_cmd_error(STR_ERROR_DEPOT_WRONG_DEPOT_TYPE); - /* Use same tile if possible when replacing. */ - if (flags & DC_AUTOREPLACE) { - /* Use same tile if possible when replacing. */ - if (HasTileAnyRoadType(tile, rti->powered_roadtypes)) return CommandCost(); + /* 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(); + } } - tile = INVALID_TILE; - for (auto &depot_tile : dep->depot_tiles) { - if (!HasTileAnyRoadType(depot_tile, rti->powered_roadtypes)) continue; - tile = depot_tile; - break; - } - - assert(tile != INVALID_TILE); - return CommandCost(); + 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. @@ -299,24 +320,36 @@ CommandCost CmdBuildRoadVehicle(DoCommandFlag flags, TileIndex tile, const Engin assert(IsRoadDepotTile(tile)); RoadType rt = e->u.road.roadtype; const RoadTypeInfo *rti = GetRoadTypeInfo(rt); + DiagDirection dir = GetRoadDepotExit(tile, RoadTramType(rt), GetRoadDepotDirection(tile)); + bool facing_south = IsValidDiagDirection(dir) ? IsDiagDirFacingSouth(dir) : false; - CommandCost check = FindDepotTileForPlacingEngine(tile, e, flags); - if (check.Failed()) return check; + 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; @@ -360,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(); } @@ -369,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); } @@ -654,7 +694,7 @@ struct RoadVehFindData { static Vehicle *EnumCheckRoadVehClose(Vehicle *v, void *data) { - if (v->type != VEH_ROAD || v->IsInDepot()) return nullptr; + if (v->type != VEH_ROAD || (v->vehstatus & VS_HIDDEN) == 0) return nullptr; RoadVehFindData *rvf = (RoadVehFindData*)data; @@ -843,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; } /** @@ -880,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; @@ -961,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) */ @@ -1065,60 +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) && IsRoadDepotTile(v->tile) && v->current_order.GetDestination() == GetDepotIndex(v->tile)) { - VehicleEnterDepot(v); + 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); + if (first && IsExtendedDepot(v->tile)) { + UpdateExtendedDepotReservation(v, false); + } + InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)); - return true; + return !visible_vehicle; } static Trackdir FollowPreviousRoadVehicle(const RoadVehicle *v, const RoadVehicle *prev, TileIndex tile, DiagDirection entry_dir, bool already_reversed) @@ -1138,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; @@ -1206,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) { @@ -1264,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) { @@ -1302,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)) { @@ -1319,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 @@ -1363,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; @@ -1419,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; @@ -1429,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 @@ -1495,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; @@ -1504,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); + } } } @@ -1515,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) { - 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 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) { @@ -1634,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; } @@ -1655,6 +1818,8 @@ static bool RoadVehController(RoadVehicle *v) return true; } + if (v->ContinueServicing()) return true; + ProcessOrders(v); v->HandleLoading(); @@ -1816,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/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index 1a23c07a23..b2f94fdda5 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -253,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; } @@ -610,7 +613,16 @@ CommandCost CmdStartStopVehicle(DoCommandFlag flags, VehicleID veh_id, bool eval } 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: { From 56af97cb341057b806f9f99b6900f61cac952d82 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Wed, 16 Jun 2021 19:40:46 +0200 Subject: [PATCH 77/78] Change: This is a testing version, so make it difficult to load savegames with other versions of OpenTTD. --- src/saveload/saveload.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index f498fa2839..e14d297b75 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -399,6 +399,8 @@ enum SaveLoadVersion : uint16_t { 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 }; From cc5fcd29e9c399be6a4d37afed07e0e0ee131843 Mon Sep 17 00:00:00 2001 From: J0anJosep Date: Mon, 12 Jul 2021 16:59:02 +0200 Subject: [PATCH 78/78] Doc: Changes in the landscape grid due to extended depots. --- docs/landscape.html | 21 ++++++++++- docs/landscape_grid.html | 81 ++++++++++++++++++++++++---------------- 2 files changed, 69 insertions(+), 33 deletions(-) diff --git a/docs/landscape.html b/docs/landscape.html index 40d73c7fe4..331678f809 100644 --- a/docs/landscape.html +++ b/docs/landscape.html @@ -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: @@ -689,6 +694,15 @@
      0 
    • +
    • 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)
    @@ -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 6e59a3acac..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 - 1OOX 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,16 +252,16 @@ 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 OOOO + OOOO OOOO + OOOO OOOO OOOO OOOO canal, river @@ -262,11 +274,16 @@ the array so you can quickly see what is used and what is not. O1OOXX XX - shipdepot - XXXX XXXX XXXX XXXX + standard ship depot + XXXX XXXX XXXX XXXX OOOO OOOO 1OOOOOX X + + extended ship depot + XXOO OOOO + 1O1OOOX X + 8 finished industry