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();