diff --git a/src/articulated_vehicles.cpp b/src/articulated_vehicles.cpp index 194f581a62..53cc72fe9b 100644 --- a/src/articulated_vehicles.cpp +++ b/src/articulated_vehicles.cpp @@ -280,6 +280,30 @@ void GetArticulatedRefitMasks(EngineID engine, bool include_initial_cargo_type, if (veh_cargoes != 0) *intersection_mask &= veh_cargoes; } } +/** + * Gets the individual refit_masks of each articulated part. + * @param engine the first part + * @return vector of cargo types + */ +std::vector GetArticulatedRefitMaskVector(EngineID engine, bool include_initial_cargo_type) +{ + std::vector output; + + const Engine *e = Engine::Get(engine); + output.push_back(GetAvailableVehicleCargoTypes(engine, include_initial_cargo_type)); + + if (!e->IsArticulatedCallbackVehicleType()) return output; + if (!HasBit(e->info.callback_mask, CBM_VEHICLE_ARTIC_ENGINE)) return output; + + for (uint i = 1; i < MAX_ARTICULATED_PARTS; i++) { + EngineID artic_engine = GetNextArticulatedPart(i, engine); + if (artic_engine == INVALID_ENGINE) break; + + output.push_back(GetAvailableVehicleCargoTypes(artic_engine, include_initial_cargo_type)); + } + + return output; +} /** * Ors the refit_masks of all articulated parts. diff --git a/src/articulated_vehicles.h b/src/articulated_vehicles.h index e287787413..5741b15565 100644 --- a/src/articulated_vehicles.h +++ b/src/articulated_vehicles.h @@ -20,6 +20,7 @@ CargoArray GetCapacityOfArticulatedParts(EngineID engine); CargoTypes GetCargoTypesOfArticulatedParts(EngineID engine); void AddArticulatedParts(Vehicle *first); void GetArticulatedRefitMasks(EngineID engine, bool include_initial_cargo_type, CargoTypes *union_mask, CargoTypes *intersection_mask); +std::vector GetArticulatedRefitMaskVector(EngineID engine, bool include_initial_cargo_type); CargoTypes GetUnionOfArticulatedRefitMasks(EngineID engine, bool include_initial_cargo_type); CargoTypes GetIntersectionOfArticulatedRefitMasks(EngineID engine, bool include_initial_cargo_type); CargoTypes GetCargoTypesOfArticulatedVehicle(const Vehicle *v, CargoID *cargo_type); diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 434f925ee5..0a0bab851c 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -225,7 +225,7 @@ static int GetIncompatibleRefitOrderIdForAutoreplace(const Vehicle *v, EngineID * CT_NO_REFIT is returned if no refit is needed * CT_INVALID is returned when both old and new vehicle got cargo capacity and refitting the new one to the old one's cargo type isn't possible */ -static CargoID GetNewCargoTypeForReplace(Vehicle *v, EngineID engine_type, bool part_of_chain) +static CargoID GetNewCargoTypeForReplace(const Vehicle *v, EngineID engine_type, bool part_of_chain) { CargoTypes available_cargo_types, union_mask; GetArticulatedRefitMasks(engine_type, true, &union_mask, &available_cargo_types); @@ -308,6 +308,163 @@ static CommandCost GetNewEngineType(const Vehicle *v, const Company *c, bool alw return CommandCost(STR_ERROR_RAIL_VEHICLE_NOT_AVAILABLE + v->type); } +static CommandCost BuildReplacementVehicleRefitFailure(EngineID e, const Vehicle *old_veh) +{ + if (!IsLocalCompany()) return CommandCost(); + + SetDParam(0, old_veh->index); + + int order_id = GetIncompatibleRefitOrderIdForAutoreplace(old_veh, e); + if (order_id != -1) { + /* Orders contained a refit order that is incompatible with the new vehicle. */ + SetDParam(1, STR_ERROR_AUTOREPLACE_INCOMPATIBLE_REFIT); + SetDParam(2, order_id + 1); // 1-based indexing for display + } else { + /* Current cargo is incompatible with the new vehicle. */ + SetDParam(1, STR_ERROR_AUTOREPLACE_INCOMPATIBLE_CARGO); + SetDParam(2, CargoSpec::Get(old_veh->cargo_type)->name); + } + + AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_AUTORENEW_FAILED, old_veh->index); + return CommandCost(); +} + +static CommandCost BuildReplacementMultiPartShipSimple(EngineID e, const Vehicle *old_veh, Vehicle **new_vehicle) +{ + /* Build the new vehicle */ + CommandCost cost = DoCommand(old_veh->tile, e | (CT_INVALID << 24), 0, DC_EXEC | DC_AUTOREPLACE, GetCmdBuildVeh(old_veh)); + if (cost.Failed()) return cost; + + Vehicle *new_veh = Vehicle::Get(_new_vehicle_id); + *new_vehicle = new_veh; + + Vehicle *v = new_veh; + const Vehicle *old = old_veh; + for (; v != nullptr && old != nullptr; v = v->Next(), old = old->Next()) { + if (old->cargo_type == CT_INVALID) continue; + + byte subtype = GetBestFittingSubType(old, v, old->cargo_type); + CommandCost refit_cost = DoCommand(0, v->index, old->cargo_type | (subtype << 8) | (1 << 16), DC_EXEC, GetCmdRefitVeh(v)); + if (refit_cost.Succeeded()) cost.AddCost(refit_cost); + } + + return CommandCost(); +} + +/** + * Builds and refits a replacement multi-part ship + * @param old_veh A ship that shall be replaced. + * @param new_vehicle Returns the newly build and refitted ship, if this is nullptr the function operates in dry-run mode + * @param all_cargoes Mask of all cargoes in old_veh + * @return cost or error + */ +static CommandCost BuildReplacementMultiPartShip(EngineID e, const Vehicle *old_veh, Vehicle **new_vehicle, CargoTypes all_cargoes) +{ + if (old_veh->engine_type == e) { + /* Easy mode, autoreplacing with same engine */ + if (new_vehicle == nullptr) return CommandCost(); // dry-run: success + return BuildReplacementMultiPartShipSimple(e, old_veh, new_vehicle); + } + + std::vector refit_mask_list = GetArticulatedRefitMaskVector(e, true); + + std::array old_cargo_vehs = {}; + bool easy_mode = true; + size_t refit_idx = 0; + for (const Vehicle *old = old_veh; old != nullptr; old = old->Next(), refit_idx++) { + if (refit_idx == refit_mask_list.size()) { + easy_mode = false; + } + if (old->cargo_type == CT_INVALID) continue; + + old_cargo_vehs[old->cargo_type] = old; + + if (easy_mode && !HasBit(refit_mask_list[refit_idx], old->cargo_type)) { + easy_mode = false; + } + } + if (easy_mode) { + if (new_vehicle == nullptr) return CommandCost(); // dry-run: success + + CommandCost cost = BuildReplacementMultiPartShipSimple(e, old_veh, new_vehicle); + if (*new_vehicle != nullptr && refit_idx < refit_mask_list.size()) { + for (Vehicle *v = (*new_vehicle)->Move(refit_idx); v != nullptr; v = v->Next(), refit_idx++) { + if (refit_idx == refit_mask_list.size()) break; + + CargoTypes available = all_cargoes & refit_mask_list[refit_idx]; + if (available == 0) continue; + CargoID c = FindFirstBit(available); + assert(old_cargo_vehs[c] != nullptr); + + byte subtype = GetBestFittingSubType(old_cargo_vehs[c], v, c); + CommandCost refit_cost = DoCommand(0, v->index, c | (subtype << 8) | (1 << 16), DC_EXEC, GetCmdRefitVeh(v)); + if (refit_cost.Succeeded()) cost.AddCost(refit_cost); + } + } + return cost; + } + + if (!VerifyAutoreplaceRefitForOrders(old_veh, e)) { + if (new_vehicle == nullptr) return CMD_ERROR; // dry-run: failure + return BuildReplacementVehicleRefitFailure(e, old_veh); + } + + std::vector output_cargoes; + CargoTypes remaining = all_cargoes; + CargoTypes todo = all_cargoes; + for (size_t i = 0; i < refit_mask_list.size(); i++) { + CargoTypes available = todo & refit_mask_list[i]; + if (available == 0) available = all_cargoes & refit_mask_list[i]; + if (available == 0) { + output_cargoes.push_back(CT_INVALID); + continue; + } + + CargoID c = FindFirstBit(available); + output_cargoes.push_back(c); + ClrBit(remaining, c); + ClrBit(todo, c); + if (todo == 0) todo = all_cargoes; + } + + if (remaining != 0) { + if (new_vehicle == nullptr) return CMD_ERROR; // dry-run: failure + if (IsLocalCompany()) { + SetDParam(0, old_veh->index); + SetDParam(1, STR_ERROR_AUTOREPLACE_INCOMPATIBLE_CARGO); + SetDParam(2, CargoSpec::Get(FindFirstBit(remaining))->name); + AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_AUTORENEW_FAILED, old_veh->index); + } + return CommandCost(); + } + + if (new_vehicle == nullptr) return CommandCost(); // dry-run: success + + /* Build the new vehicle */ + CommandCost cost = DoCommand(old_veh->tile, e | (CT_INVALID << 24), 0, DC_EXEC | DC_AUTOREPLACE, GetCmdBuildVeh(old_veh)); + if (cost.Failed()) return cost; + + Vehicle *new_veh = Vehicle::Get(_new_vehicle_id); + *new_vehicle = new_veh; + + size_t i = 0; + for (Vehicle *v = new_veh; v != nullptr && i < output_cargoes.size(); v = v->Next(), i++) { + CargoID c = output_cargoes[i]; + if (c == CT_INVALID) continue; + + assert(old_cargo_vehs[c] != nullptr); + byte subtype = GetBestFittingSubType(old_cargo_vehs[c], v, c); + CommandCost refit_cost = DoCommand(0, v->index, c | (subtype << 8) | (1 << 16), DC_EXEC, GetCmdRefitVeh(v)); + if (refit_cost.Succeeded()) cost.AddCost(refit_cost); + } + return cost; +} + +bool AutoreplaceMultiPartShipWouldSucceed(EngineID e, const Vehicle *old_veh, CargoTypes all_cargoes) +{ + return BuildReplacementMultiPartShip(e, old_veh, nullptr, all_cargoes).Succeeded(); // dry-run mode +} + /** * Builds and refits a replacement vehicle * Important: The old vehicle is still in the original vehicle chain (used for determining the cargo when the old vehicle did not carry anything, but the new one does) @@ -317,7 +474,7 @@ static CommandCost GetNewEngineType(const Vehicle *v, const Company *c, bool alw * @param same_type_only Only replace with same engine type. * @return cost or error */ -static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehicle, bool part_of_chain, bool same_type_only) +static CommandCost BuildReplacementVehicle(const Vehicle *old_veh, Vehicle **new_vehicle, bool part_of_chain, bool same_type_only) { *new_vehicle = nullptr; @@ -328,26 +485,23 @@ static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehic if (cost.Failed()) return cost; if (e == INVALID_ENGINE) return CommandCost(); // neither autoreplace is set, nor autorenew is triggered + if (old_veh->type == VEH_SHIP && old_veh->Next() != nullptr) { + CargoTypes cargoes = 0; + for (const Vehicle *u = old_veh; u != nullptr; u = u->Next()) { + if (u->cargo_type != CT_INVALID && u->GetEngine()->CanCarryCargo()) { + SetBit(cargoes, u->cargo_type); + } + } + if (!HasAtMostOneBit(cargoes)) { + /* Old ship has more than one cargo, special handling */ + return BuildReplacementMultiPartShip(e, old_veh, new_vehicle, cargoes); + } + } + /* Does it need to be refitted */ CargoID refit_cargo = GetNewCargoTypeForReplace(old_veh, e, part_of_chain); if (refit_cargo == CT_INVALID) { - if (!IsLocalCompany()) return CommandCost(); - - SetDParam(0, old_veh->index); - - int order_id = GetIncompatibleRefitOrderIdForAutoreplace(old_veh, e); - if (order_id != -1) { - /* Orders contained a refit order that is incompatible with the new vehicle. */ - SetDParam(1, STR_ERROR_AUTOREPLACE_INCOMPATIBLE_REFIT); - SetDParam(2, order_id + 1); // 1-based indexing for display - } else { - /* Current cargo is incompatible with the new vehicle. */ - SetDParam(1, STR_ERROR_AUTOREPLACE_INCOMPATIBLE_CARGO); - SetDParam(2, CargoSpec::Get(old_veh->cargo_type)->name); - } - - AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_AUTORENEW_FAILED, old_veh->index); - return CommandCost(); + return BuildReplacementVehicleRefitFailure(e, old_veh); } /* Build the new vehicle */ diff --git a/src/autoreplace_func.h b/src/autoreplace_func.h index 519165bae1..404d28e66a 100644 --- a/src/autoreplace_func.h +++ b/src/autoreplace_func.h @@ -99,4 +99,6 @@ bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company); CommandCost CopyHeadSpecificThings(Vehicle*, Vehicle*, DoCommandFlag); +bool AutoreplaceMultiPartShipWouldSucceed(EngineID e, const Vehicle *old_veh, CargoTypes all_cargoes); + #endif /* AUTOREPLACE_FUNC_H */ diff --git a/src/vehicle.cpp b/src/vehicle.cpp index f0ac91dfb1..4ae7bd3efc 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -288,6 +288,22 @@ bool Vehicle::NeedsServicing() const /* Check refittability */ CargoTypes available_cargo_types, union_mask; GetArticulatedRefitMasks(new_engine, true, &union_mask, &available_cargo_types); + + /* Is this a multi-cargo ship? */ + if (union_mask != 0 && v->type == VEH_SHIP && v->Next() != nullptr) { + CargoTypes cargoes = 0; + for (const Vehicle *u = v; u != nullptr; u = u->Next()) { + if (u->cargo_type != CT_INVALID && u->GetEngine()->CanCarryCargo()) { + SetBit(cargoes, u->cargo_type); + } + } + if (!HasAtMostOneBit(cargoes)) { + /* Ship has more than one cargo, special handling */ + if (!AutoreplaceMultiPartShipWouldSucceed(new_engine, v, cargoes)) continue; + union_mask = 0; + } + } + /* Is there anything to refit? */ if (union_mask != 0) { CargoID cargo_type; diff --git a/src/vehicle_func.h b/src/vehicle_func.h index 3815de6b88..5df1f5159d 100644 --- a/src/vehicle_func.h +++ b/src/vehicle_func.h @@ -126,7 +126,7 @@ void VehicleLengthChanged(const Vehicle *u); void ResetVehicleHash(); void ResetVehicleColourMap(); -byte GetBestFittingSubType(Vehicle *v_from, Vehicle *v_for, CargoID dest_cargo_type); +byte GetBestFittingSubType(const Vehicle *v_from, Vehicle *v_for, CargoID dest_cargo_type); void ViewportAddVehicles(DrawPixelInfo *dpi, bool update_vehicles); void ViewportMapDrawVehicles(DrawPixelInfo *dpi, Viewport *vp); diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index bd02a3b335..7d0061500b 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -495,7 +495,7 @@ static const uint MAX_REFIT_CYCLE = 256; * @param dest_cargo_type Destination cargo type. * @return the best sub type */ -byte GetBestFittingSubType(Vehicle *v_from, Vehicle *v_for, CargoID dest_cargo_type) +byte GetBestFittingSubType(const Vehicle *v_from, Vehicle *v_for, CargoID dest_cargo_type) { v_from = v_from->GetFirstEnginePart(); v_for = v_for->GetFirstEnginePart();