mirror of
https://github.com/JGRennison/OpenTTD-patches.git
synced 2024-10-31 15:20:10 +00:00
Autoreplace: Add support for multi-cargo ships
This commit is contained in:
parent
69b2ca9983
commit
7c28ee0213
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user