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)) {