diff --git a/src/economy.cpp b/src/economy.cpp index 08445d03d8..018a291e64 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -1675,8 +1675,9 @@ static void LoadUnloadVehicle(Vehicle *front) StationID last_visited = front->last_station_visited; Station *st = Station::Get(last_visited); - TileIndex station_tile = front->tile; - if (front->type == VEH_TRAIN) station_tile = Train::From(front)->GetStationLoadingVehicle()->tile; + Vehicle *station_vehicle = front; + if (front->type == VEH_TRAIN) station_vehicle = Train::From(front)->GetStationLoadingVehicle(); + TileIndex station_tile = station_vehicle->tile; bool pull_through_mode = false; bool load_unload_not_yet_in_station = false; @@ -1691,12 +1692,17 @@ static void LoadUnloadVehicle(Vehicle *front) pull_through_mode = false; break; } + /* Disallow through-load when any part of train is in a depot, to prevent cheating */ + if (Train::From(v)->IsInDepot()) { + pull_through_mode = false; + break; + } } } } int platform_length_left = 0; if (pull_through_mode) { - platform_length_left = st->GetPlatformLength(station_tile) * TILE_SIZE; + platform_length_left = st->GetPlatformLength(station_tile, ReverseDiagDir(DirToDiagDir(station_vehicle->direction))) * TILE_SIZE - GetTileMarginInFrontOfTrain(Train::From(station_vehicle)); } else if (front->type == VEH_TRAIN) { platform_length_left = st->GetPlatformLength(station_tile) * TILE_SIZE - front->GetGroundVehicleCache()->cached_total_length; } @@ -1707,10 +1713,10 @@ static void LoadUnloadVehicle(Vehicle *front) CargoArray consist_capleft; bool should_reserve_consist = false; bool reserve_consist_cargo_type_loading = false; - if ((_settings_game.order.improved_load && use_autorefit) || pull_through_mode) { + if (_settings_game.order.improved_load && use_autorefit) { if (front->cargo_payment == NULL) should_reserve_consist = true; } else { - if ((front->current_order.GetLoadType() & OLFB_FULL_LOAD) || (front->current_order.GetLoadType() == OLFB_CARGO_TYPE_LOAD)) { + if ((front->current_order.GetLoadType() & OLFB_FULL_LOAD) || (front->current_order.GetLoadType() == OLFB_CARGO_TYPE_LOAD) || pull_through_mode) { should_reserve_consist = true; reserve_consist_cargo_type_loading = (front->current_order.GetLoadType() == OLFB_CARGO_TYPE_LOAD); } diff --git a/src/ground_vehicle.hpp b/src/ground_vehicle.hpp index d0c1e9baf9..78e539dbb7 100644 --- a/src/ground_vehicle.hpp +++ b/src/ground_vehicle.hpp @@ -418,8 +418,6 @@ protected: } } - if (this->current_order.IsType(OT_LOADING_ADVANCE)) tempmax = min(tempmax, 15); - if (this->cur_speed > max_speed) { tempmax = max(this->cur_speed - (this->cur_speed / 10) - 1, max_speed); } diff --git a/src/lang/english.txt b/src/lang/english.txt index 08e52dcccd..143aca4c32 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -4270,6 +4270,9 @@ STR_VEHICLE_DETAILS_AIRCRAFT_RENAME :{BLACK}Name air STR_VEHICLE_INFO_AGE_RUNNING_COST_YR :{BLACK}Age: {LTBLUE}{STRING2}{BLACK} Running Cost: {LTBLUE}{CURRENCY_LONG}/yr +STR_VEHICLE_LOAD_THROUGH_ABORTED_INSUFFICIENT_TRACK :{WHITE}{VEHICLE}: through load aborted due to insufficient track at {STATION} +STR_VEHICLE_LOAD_THROUGH_ABORTED_DEPOT :{WHITE}{VEHICLE}: through load aborted due to depot at {STATION} + STR_RUNNING :{LTBLUE}Running STR_NEED_REPAIR :{ORANGE}Vehicle needs repair - max speed reduced to {VELOCITY} STR_CURRENT_STATUS :{BLACK}Current status: {STRING3} diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 0aad608930..5dc95ad573 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -38,6 +38,7 @@ #include "programmable_signals.h" #include "spritecache.h" #include "core/container_func.hpp" +#include "news_func.h" #include "table/strings.h" #include "table/railtypes.h" @@ -3180,10 +3181,28 @@ 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; - if (u->current_order.IsType(OT_LOADING_ADVANCE)) u->LeaveStation(); - Train *v = Train::From(u); + auto abort_load_through = [&](bool leave_station) { + if (_local_company == v->owner) { + SetDParam(0, v->index); + SetDParam(1, v->current_order.GetDestination()); + AddNewsItem(STR_VEHICLE_LOAD_THROUGH_ABORTED_DEPOT, NT_ADVICE, NF_INCOLOUR | NF_SMALL | NF_VEHICLE_PARAM0, + NR_VEHICLE, v->index, + NR_STATION, v->current_order.GetDestination()); + } + if (leave_station) { + v->LeaveStation(); + /* Only advance to next order if we are loading at the current one */ + const Order *order = v->GetOrder(v->cur_implicit_order_index); + if (order != NULL && order->IsType(OT_GOTO_STATION) && order->GetDestination() == v->last_station_visited) { + v->IncrementImplicitOrderIndex(); + } + } + }; + + if (v->IsFrontEngine() && v->current_order.IsType(OT_LOADING_ADVANCE)) abort_load_through(true); + /* depot direction */ DiagDirection dir = GetRailDepotDirection(tile); @@ -3204,6 +3223,13 @@ static VehicleEnterTileStatus VehicleEnter_Track(Vehicle *u, TileIndex tile, int } else if (_fractcoords_enter[dir] == fract_coord) { if (DiagDirToDir(ReverseDiagDir(dir)) == v->direction) { /* enter the depot */ + + if (v->IsFrontEngine() && v->current_order.IsType(OT_LOADING_ADVANCE)) { + abort_load_through(true); + } else if (v->IsFrontEngine() && HasBit(v->flags, VRF_BEYOND_PLATFORM_END)) { + abort_load_through(false); + } + v->track = TRACK_BIT_DEPOT, v->vehstatus |= VS_HIDDEN; // hide it v->direction = ReverseDir(v->direction); diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 015dcfd228..36336e7fa0 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -3309,7 +3309,7 @@ static VehicleEnterTileStatus VehicleEnter_Station(Vehicle *v, TileIndex tile, i int station_ahead; int station_length; - int stop = GetTrainStopLocation(station_id, tile, Train::From(v), &station_ahead, &station_length); + int stop = GetTrainStopLocation(station_id, tile, Train::From(v), &station_ahead, &station_length, x, y); /* Stop whenever that amount of station ahead + the distance from the * begin of the platform to the stop location is longer than the length diff --git a/src/train.h b/src/train.h index 23605aeebd..f7f85fb24a 100644 --- a/src/train.h +++ b/src/train.h @@ -79,7 +79,6 @@ bool TryPathReserve(Train *v, bool mark_as_stuck = false, bool first_tile_okay = void DeleteVisibleTrain(Train *v); -int GetTrainStopLocation(StationID station_id, TileIndex tile, Train *v, int *station_ahead, int *station_length); void CheckBreakdownFlags(Train *v); void GetTrainSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); @@ -406,6 +405,20 @@ CommandCost CmdMoveVirtualRailVehicle(TileIndex, DoCommandFlag, uint32, uint32, Train* CmdBuildVirtualRailWagon(const Engine*); Train* CmdBuildVirtualRailVehicle(EngineID, bool lax_engine_check, StringID &error); +int GetTileMarginInFrontOfTrain(const Train *v, int x_pos, int y_pos); + +inline int GetTileMarginInFrontOfTrain(const Train *v) +{ + return GetTileMarginInFrontOfTrain(v, v->x_pos, v->y_pos); +} + +int GetTrainStopLocation(StationID station_id, TileIndex tile, Train *v, int *station_ahead, int *station_length, int x_pos, int y_pos); + +inline int GetTrainStopLocation(StationID station_id, TileIndex tile, Train *v, int *station_ahead, int *station_length) +{ + return GetTrainStopLocation(station_id, tile, v, station_ahead, station_length, v->x_pos, v->y_pos); +} + #define FOR_ALL_TRAINS(var) FOR_ALL_VEHICLES_OF_TYPE(Train, var) #endif /* TRAIN_H */ diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 56e5637e68..04a4395f79 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -51,11 +51,14 @@ static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool force_res, bool *p_got_reservation, bool mark_stuck); static bool TrainApproachingLineEnd(Train *v, bool signal, bool reverse); static bool TrainCheckIfLineEnds(Train *v, bool reverse = true); +static bool TrainCanLeaveTile(const Train *v); +static inline bool CheckCompatibleRail(const Train *v, TileIndex tile); bool TrainController(Train *v, Vehicle *nomove, bool reverse = true); // Also used in vehicle_sl.cpp. static TileIndex TrainApproachingCrossingTile(const Train *v); static void CheckIfTrainNeedsService(Train *v); static void CheckNextTrainTile(Train *v); TileIndex VehiclePosTraceRestrictPreviousSignalCallback(const Train *v, const void *); +static void TrainEnterStation(Train *v, StationID station); static const byte _vehicle_initial_x_fract[4] = {10, 8, 4, 8}; static const byte _vehicle_initial_y_fract[4] = { 8, 4, 8, 10}; @@ -331,17 +334,57 @@ void Train::ConsistChanged(ConsistChangeFlags allowed_changes) } } +/** + * Get the fraction of the vehicle's current tile which is in front of it. + * This is equal to how many more steps it could travel without having to stop/reverse if it was an end of line. + * + * See also wrapper without x_pos, y_pos in train.h + * + * @param v the vehicle to use (not required to be the front) + * @param x_pos vehicle x position + * @param y_pos vehicle y position + * @return the fraction of the current tile in front of the vehicle + */ +int GetTileMarginInFrontOfTrain(const Train *v, int x_pos, int y_pos) +{ + if (IsDiagonalDirection(v->direction)) { + DiagDirection dir = DirToDiagDir(v->direction); + int offset = ((DiagDirToAxis(dir) == AXIS_X) ? x_pos : y_pos) & 0xF; + return ((dir == DIAGDIR_SE || dir == DIAGDIR_SW) ? TILE_SIZE - 1 - offset : offset) - ((v->gcache.cached_veh_length + 1) / 2); + } else { + /* Calc position within the current tile */ + uint x = x_pos & 0xF; + uint y = y_pos & 0xF; + + /* for non-diagonal directions, x will be 1, 3, 5, ..., 15 */ + switch (v->direction) { + case DIR_N : x = ~x + ~y + 25; break; + case DIR_E : x = ~x + y + 9; break; + case DIR_S : x = x + y - 7; break; + case DIR_W : x = ~y + x + 9; break; + default: break; + } + x >>= 1; // x is now in range 0 ... 7 + return (TILE_SIZE / 2) - 1 - x - (v->gcache.cached_veh_length + 1) / 2; + } +} + /** * Get the stop location of (the center) of the front vehicle of a train at * a platform of a station. + * + * See also wrapper without x_pos, y_pos in train.h + * * @param station_id the ID of the station where we're stopping * @param tile the tile where the vehicle currently is * @param v the vehicle to get the stop location of * @param station_ahead 'return' the amount of 1/16th tiles in front of the train * @param station_length 'return' the station length in 1/16th tiles + * @param x_pos vehicle x position + * @param y_pos vehicle y position * @return the location, calculated from the begin of the station to stop at. */ -int GetTrainStopLocation(StationID station_id, TileIndex tile, Train *v, int *station_ahead, int *station_length) +int GetTrainStopLocation(StationID station_id, TileIndex tile, Train *v, int *station_ahead, int *station_length, int x_pos, int y_pos) { Train *front = v->First(); const Station *st = Station::Get(station_id); @@ -419,7 +462,38 @@ int GetTrainStopLocation(StationID station_id, TileIndex tile, Train *v, int *st /* Subtract half the front vehicle length of the train so we get the real * stop location of the train. */ - return stop - ((v->gcache.cached_veh_length + 1) / 2) + adjust; + int result = stop - ((v->gcache.cached_veh_length + 1) / 2) + adjust; + + if (osl == OSL_PLATFORM_THROUGH && v != front) { + /* Check front of train for obstructions */ + + if (TrainCanLeaveTile(front)) { + /* Determine the non-diagonal direction in which we will exit this tile */ + DiagDirection dir = TrainExitDir(front->direction, front->track); + /* Calculate next tile */ + TileIndex tile = front->tile + TileOffsByDiagDir(dir); + + /* Determine the track status on the next tile */ + TrackStatus ts = GetTileTrackStatus(tile, TRANSPORT_RAIL, 0, ReverseDiagDir(dir)); + TrackdirBits trackdirbits = TrackStatusToTrackdirBits(ts) & DiagdirReachesTrackdirs(dir); + + /* mask unreachable track bits if we are forbidden to do 90deg turns */ + TrackBits bits = TrackdirBitsToTrackBits(trackdirbits); + if (_settings_game.pf.forbid_90_deg) { + bits &= ~TrackCrossesTracks(FindFirstTrack(front->track)); + } + + if (bits == TRACK_BIT_NONE || !CheckCompatibleRail(front, tile) || IsRailDepotTile(tile) || + (KillFirstBit(trackdirbits) == TRACKDIR_BIT_NONE && HasOnewaySignalBlockingTrackdir(tile, FindFirstTrackdir(trackdirbits)))) { + /* next tile is an effective dead end */ + int current_platform_remaining = *station_ahead - TILE_SIZE + GetTileMarginInFrontOfTrain(v); + int limit = GetTileMarginInFrontOfTrain(front) + (*station_length - current_platform_remaining) - ((v->gcache.cached_veh_length + 1) / 2); + result = min(limit, result); + } + } + } + + return result; } @@ -548,6 +622,8 @@ int Train::GetCurrentMaxSpeed() const max_speed = min(max_speed, this->GetBreakdownSpeed()); } + if (this->current_order.IsType(OT_LOADING_ADVANCE)) max_speed = min(max_speed, 15); + return min(max_speed, this->gcache.cached_max_track_speed); } @@ -2054,7 +2130,27 @@ void ReverseTrainDirection(Train *v) InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); } - if (v->current_order.IsType(OT_LOADING_ADVANCE)) v->LeaveStation(); + if (_local_company == v->owner && (v->current_order.IsType(OT_LOADING_ADVANCE) || HasBit(v->flags, VRF_BEYOND_PLATFORM_END))) { + SetDParam(0, v->index); + SetDParam(1, v->current_order.GetDestination()); + AddNewsItem(STR_VEHICLE_LOAD_THROUGH_ABORTED_INSUFFICIENT_TRACK, NT_ADVICE, NF_INCOLOUR | NF_SMALL | NF_VEHICLE_PARAM0, + NR_VEHICLE, v->index, + NR_STATION, v->current_order.GetDestination()); + } + if (v->current_order.IsType(OT_LOADING_ADVANCE)) { + v->LeaveStation(); + + /* Only advance to next order if we are loading at the current one */ + const Order *order = v->GetOrder(v->cur_implicit_order_index); + if (order != NULL && order->IsType(OT_GOTO_STATION) && order->GetDestination() == v->last_station_visited) { + v->IncrementImplicitOrderIndex(); + } + } + + for (Train *u = v; u != nullptr; u = u->Next()) { + ClrBit(u->flags, VRF_BEYOND_PLATFORM_END); + ClrBit(u->flags, VRF_NOT_YET_IN_PLATFORM); + } v->reverse_distance = 0; @@ -2200,7 +2296,8 @@ CommandCost CmdReverseTrainDirection(TileIndex tile, DoCommandFlag flags, uint32 /* not a station || different station --> leave the station */ if (!IsTileType(last->tile, MP_STATION) || !IsTileType(v->tile, MP_STATION) || GetStationIndex(last->tile) != GetStationIndex(v->tile) || - HasBit(v->flags, VRF_BEYOND_PLATFORM_END)) { + HasBit(v->flags, VRF_BEYOND_PLATFORM_END) || + v->current_order.IsType(OT_LOADING_ADVANCE)) { v->LeaveStation(); } }