Autoreplace: Add support for multi-cargo ships

This commit is contained in:
Jonathan G Rennison 2023-09-01 23:35:42 +01:00
parent 69b2ca9983
commit 7c28ee0213
7 changed files with 218 additions and 21 deletions

View File

@ -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<CargoTypes> GetArticulatedRefitMaskVector(EngineID engine, bool include_initial_cargo_type)
{
std::vector<CargoTypes> 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.

View File

@ -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<CargoTypes> 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);

View File

@ -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<CargoTypes> refit_mask_list = GetArticulatedRefitMaskVector(e, true);
std::array<const Vehicle *, NUM_CARGO> 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 <CargoID> 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 */

View File

@ -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 */

View File

@ -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;

View File

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

View File

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