Fix: Trivial autoreplace of mixed cargo articulated engines (#11253)

Do not fail autoreplace/autorenew of mixed cargo articulated engines
due to an inability to refit to mixed cargoes, when no refit is
required because the target engine already has a suitable set of cargoes.
Notably, this allows autorenew (autoreplace to same engine type)
to succeed.
This commit is contained in:
Jonathan G Rennison 2023-11-20 13:16:28 +00:00 committed by GitHub
parent 96d98d08c8
commit 9822fa6584
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 66 additions and 15 deletions

View File

@ -8,6 +8,7 @@
/** @file articulated_vehicles.cpp Implementation of articulated vehicles. */ /** @file articulated_vehicles.cpp Implementation of articulated vehicles. */
#include "stdafx.h" #include "stdafx.h"
#include "core/bitmath_func.hpp"
#include "core/random_func.hpp" #include "core/random_func.hpp"
#include "train.h" #include "train.h"
#include "roadveh.h" #include "roadveh.h"
@ -161,6 +162,35 @@ CargoArray GetCapacityOfArticulatedParts(EngineID engine)
return capacity; return capacity;
} }
/**
* Get the cargo mask of the parts of a given engine.
* @param engine The engine to get the capacities from.
* @return The cargo mask.
*/
CargoTypes GetCargoTypesOfArticulatedParts(EngineID engine)
{
CargoTypes cargoes = 0;
const Engine *e = Engine::Get(engine);
CargoID cargo_type;
uint16_t cargo_capacity = GetVehicleDefaultCapacity(engine, &cargo_type);
if (cargo_type < NUM_CARGO && cargo_capacity > 0) SetBit(cargoes, cargo_type);
if (!e->IsGroundVehicle()) return cargoes;
if (!HasBit(e->info.callback_mask, CBM_VEHICLE_ARTIC_ENGINE)) return cargoes;
for (uint i = 1; i < MAX_ARTICULATED_PARTS; i++) {
EngineID artic_engine = GetNextArticulatedPart(i, engine);
if (artic_engine == INVALID_ENGINE) break;
cargo_capacity = GetVehicleDefaultCapacity(artic_engine, &cargo_type);
if (cargo_type < NUM_CARGO && cargo_capacity > 0) SetBit(cargoes, cargo_type);
}
return cargoes;
}
/** /**
* Checks whether any of the articulated parts is refittable * Checks whether any of the articulated parts is refittable
* @param engine the first part * @param engine the first part
@ -226,22 +256,26 @@ CargoTypes GetUnionOfArticulatedRefitMasks(EngineID engine, bool include_initial
} }
/** /**
* Tests if all parts of an articulated vehicle are refitted to the same cargo. * Get cargo mask of all cargoes carried by an articulated vehicle.
* Note: Vehicles not carrying anything are ignored * Note: Vehicles not carrying anything are ignored
* @param v the first vehicle in the chain * @param v the first vehicle in the chain
* @param cargo_type returns the common CargoID if needed. (CT_INVALID if no part is carrying something or they are carrying different things) * @param cargo_type returns the common CargoID if needed. (CT_INVALID if no part is carrying something or they are carrying different things)
* @return true if some parts are carrying different cargoes, false if all parts are carrying the same (nothing is also the same) * @return cargo mask, may be 0 if the no vehicle parts have cargo capacity
*/ */
bool IsArticulatedVehicleCarryingDifferentCargoes(const Vehicle *v, CargoID *cargo_type) CargoTypes GetCargoTypesOfArticulatedVehicle(const Vehicle *v, CargoID *cargo_type)
{ {
CargoTypes cargoes = 0;
CargoID first_cargo = CT_INVALID; CargoID first_cargo = CT_INVALID;
do { do {
if (IsValidCargoID(v->cargo_type) && v->GetEngine()->CanCarryCargo()) { if (IsValidCargoID(v->cargo_type) && v->GetEngine()->CanCarryCargo()) {
SetBit(cargoes, v->cargo_type);
if (!IsValidCargoID(first_cargo)) first_cargo = v->cargo_type; if (!IsValidCargoID(first_cargo)) first_cargo = v->cargo_type;
if (first_cargo != v->cargo_type) { if (first_cargo != v->cargo_type) {
if (cargo_type != nullptr) *cargo_type = CT_INVALID; if (cargo_type != nullptr) {
return true; *cargo_type = CT_INVALID;
cargo_type = nullptr;
}
} }
} }
@ -249,7 +283,7 @@ bool IsArticulatedVehicleCarryingDifferentCargoes(const Vehicle *v, CargoID *car
} while (v != nullptr); } while (v != nullptr);
if (cargo_type != nullptr) *cargo_type = first_cargo; if (cargo_type != nullptr) *cargo_type = first_cargo;
return false; return cargoes;
} }
/** /**

View File

@ -15,10 +15,11 @@
uint CountArticulatedParts(EngineID engine_type, bool purchase_window); uint CountArticulatedParts(EngineID engine_type, bool purchase_window);
CargoArray GetCapacityOfArticulatedParts(EngineID engine); CargoArray GetCapacityOfArticulatedParts(EngineID engine);
CargoTypes GetCargoTypesOfArticulatedParts(EngineID engine);
void AddArticulatedParts(Vehicle *first); void AddArticulatedParts(Vehicle *first);
void GetArticulatedRefitMasks(EngineID engine, bool include_initial_cargo_type, CargoTypes *union_mask, CargoTypes *intersection_mask); void GetArticulatedRefitMasks(EngineID engine, bool include_initial_cargo_type, CargoTypes *union_mask, CargoTypes *intersection_mask);
CargoTypes GetUnionOfArticulatedRefitMasks(EngineID engine, bool include_initial_cargo_type); CargoTypes GetUnionOfArticulatedRefitMasks(EngineID engine, bool include_initial_cargo_type);
bool IsArticulatedVehicleCarryingDifferentCargoes(const Vehicle *v, CargoID *cargo_type); CargoTypes GetCargoTypesOfArticulatedVehicle(const Vehicle *v, CargoID *cargo_type);
bool IsArticulatedVehicleRefittable(EngineID engine); bool IsArticulatedVehicleRefittable(EngineID engine);
bool IsArticulatedEngine(EngineID engine_type); bool IsArticulatedEngine(EngineID engine_type);
void CheckConsistencyOfArticulatedVehicle(const Vehicle *v); void CheckConsistencyOfArticulatedVehicle(const Vehicle *v);

View File

@ -16,6 +16,7 @@
#include "autoreplace_func.h" #include "autoreplace_func.h"
#include "autoreplace_gui.h" #include "autoreplace_gui.h"
#include "articulated_vehicles.h" #include "articulated_vehicles.h"
#include "core/bitmath_func.hpp"
#include "core/random_func.hpp" #include "core/random_func.hpp"
#include "vehiclelist.h" #include "vehiclelist.h"
#include "road.h" #include "road.h"
@ -236,7 +237,15 @@ static CargoID GetNewCargoTypeForReplace(Vehicle *v, EngineID engine_type, bool
if (union_mask == 0) return CT_NO_REFIT; // Don't try to refit an engine with no cargo capacity if (union_mask == 0) return CT_NO_REFIT; // Don't try to refit an engine with no cargo capacity
CargoID cargo_type; CargoID cargo_type;
if (IsArticulatedVehicleCarryingDifferentCargoes(v, &cargo_type)) return CT_INVALID; // We cannot refit to mixed cargoes in an automated way CargoTypes cargo_mask = GetCargoTypesOfArticulatedVehicle(v, &cargo_type);
if (!HasAtMostOneBit(cargo_mask)) {
CargoTypes new_engine_default_cargoes = GetCargoTypesOfArticulatedParts(engine_type);
if ((cargo_mask & new_engine_default_cargoes) == cargo_mask) {
return CT_NO_REFIT; // engine_type is already a mixed cargo type which matches the incoming vehicle by default, no refit required
}
return CT_INVALID; // We cannot refit to mixed cargoes in an automated way
}
if (!IsValidCargoID(cargo_type)) { if (!IsValidCargoID(cargo_type)) {
if (v->type != VEH_TRAIN) return CT_NO_REFIT; // If the vehicle does not carry anything at all, every replacement is fine. if (v->type != VEH_TRAIN) return CT_NO_REFIT; // If the vehicle does not carry anything at all, every replacement is fine.

View File

@ -229,13 +229,20 @@ bool Vehicle::NeedsServicing() const
/* Is there anything to refit? */ /* Is there anything to refit? */
if (union_mask != 0) { if (union_mask != 0) {
CargoID cargo_type; CargoID cargo_type;
/* We cannot refit to mixed cargoes in an automated way */ CargoTypes cargo_mask = GetCargoTypesOfArticulatedVehicle(v, &cargo_type);
if (IsArticulatedVehicleCarryingDifferentCargoes(v, &cargo_type)) continue; if (!HasAtMostOneBit(cargo_mask)) {
CargoTypes new_engine_default_cargoes = GetCargoTypesOfArticulatedParts(new_engine);
/* Did the old vehicle carry anything? */ if ((cargo_mask & new_engine_default_cargoes) != cargo_mask) {
if (IsValidCargoID(cargo_type)) { /* We cannot refit to mixed cargoes in an automated way */
/* We can't refit the vehicle to carry the cargo we want */ continue;
if (!HasBit(available_cargo_types, cargo_type)) continue; }
/* engine_type is already a mixed cargo type which matches the incoming vehicle by default, no refit required */
} else {
/* Did the old vehicle carry anything? */
if (IsValidCargoID(cargo_type)) {
/* We can't refit the vehicle to carry the cargo we want */
if (!HasBit(available_cargo_types, cargo_type)) continue;
}
} }
} }