diff --git a/src/tbtr_template_vehicle.cpp b/src/tbtr_template_vehicle.cpp index 160b02214e..e13abea6ce 100644 --- a/src/tbtr_template_vehicle.cpp +++ b/src/tbtr_template_vehicle.cpp @@ -34,6 +34,7 @@ #include "table/train_cmd.h" #include "tbtr_template_vehicle.h" +#include "tbtr_template_vehicle_func.h" // since doing stuff with sprites #include "newgrf_spritegroup.h" @@ -54,6 +55,7 @@ robin_hood::unordered_flat_map _template_replacement_index; robin_hood::unordered_flat_map _template_replacement_index_recursive; static void ReindexTemplateReplacementsRecursive(); +static void MarkTrainsInGroupAsPendingTemplateReplacement(GroupID gid, const TemplateVehicle *tv); void TemplateVehicleImageDimensions::SetFromTrain(const Train *t) { @@ -147,6 +149,7 @@ TemplateReplacement::~TemplateReplacement() _template_replacement_index.erase(this->Group()); ReindexTemplateReplacementsRecursive(); + MarkTrainsInGroupAsPendingTemplateReplacement(this->Group(), nullptr); } void TemplateReplacement::PreCleanPool() @@ -155,6 +158,80 @@ void TemplateReplacement::PreCleanPool() _template_replacement_index_recursive.clear(); } +bool ShouldServiceTrainForTemplateReplacement(const Train *t, const TemplateVehicle *tv) +{ + const Company *c = Company::Get(t->owner); + if (tv->IsReplaceOldOnly() && !t->NeedsAutorenewing(c, false)) return false; + Money needed_money = c->settings.engine_renew_money; + if (needed_money > c->money) return false; + bool need_replacement = !TrainMatchesTemplate(t, tv); + if (need_replacement) { + /* Check money. + * We want 2*(the price of the whole template) without looking at the value of the vehicle(s) we are going to sell, or not need to buy. */ + for (const TemplateVehicle *tv_unit = tv; tv_unit != nullptr; tv_unit = tv_unit->GetNextUnit()) { + if (!HasBit(Engine::Get(tv->engine_type)->company_avail, t->owner)) return false; + needed_money += 2 * Engine::Get(tv->engine_type)->GetCost(); + } + return needed_money <= c->money; + } else if (!TrainMatchesTemplateRefit(t, tv) && tv->refit_as_template) { + return true; + } else { + return false; + } +} + +static void MarkTrainsInGroupAsPendingTemplateReplacement(GroupID gid, const TemplateVehicle *tv) +{ + std::vector groups; + groups.push_back(gid); + + Owner owner = Group::Get(gid)->owner; + + for (const Group *group : Group::Iterate()) { + if (group->vehicle_type != VEH_TRAIN || group->owner != owner || group->index == gid) continue; + + auto is_descendant = [gid](const Group *g) -> bool { + while (true) { + if (g->parent == INVALID_GROUP) return false; + if (g->parent == gid) { + /* If this group has its own template defined, it's not a descendant for template inheriting purposes */ + if (_template_replacement_index.find(g->index) != _template_replacement_index.end()) return false; + return true; + } + g = Group::Get(g->parent); + } + + NOT_REACHED(); + }; + if (is_descendant(group)) { + groups.push_back(group->index); + } + } + + std::sort(groups.begin(), groups.end()); + + for (Train *t : Train::Iterate()) { + if (!t->IsFrontEngine() || t->owner != owner || t->group_id >= NEW_GROUP) continue; + + if (std::binary_search(groups.begin(), groups.end(), t->group_id)) { + SB(t->vehicle_flags, VF_REPLACEMENT_PENDING, 1, (tv != nullptr && ShouldServiceTrainForTemplateReplacement(t, tv)) ? 1 : 0); + } + } +} + +void MarkTrainsUsingTemplateAsPendingTemplateReplacement(const TemplateVehicle *tv) +{ + Owner owner = tv->owner; + + for (Train *t : Train::Iterate()) { + if (!t->IsFrontEngine() || t->owner != owner || t->group_id >= NEW_GROUP) continue; + + if (GetTemplateIDByGroupIDRecursive(t->group_id) == tv->index) { + SB(t->vehicle_flags, VF_REPLACEMENT_PENDING, 1, ShouldServiceTrainForTemplateReplacement(t, tv) ? 1 : 0); + } + } +} + TemplateReplacement *GetTemplateReplacementByGroupID(GroupID gid) { if (GetTemplateIDByGroupID(gid) == INVALID_TEMPLATE) return nullptr; @@ -190,11 +267,13 @@ bool IssueTemplateReplacement(GroupID gid, TemplateID tid) tr->SetTemplate(tid); _template_replacement_index[gid] = tid; ReindexTemplateReplacementsRecursive(); + MarkTrainsInGroupAsPendingTemplateReplacement(gid, TemplateVehicle::Get(tid)); return true; } else if (TemplateReplacement::CanAllocateItem()) { tr = new TemplateReplacement(gid, tid); _template_replacement_index[gid] = tid; ReindexTemplateReplacementsRecursive(); + MarkTrainsInGroupAsPendingTemplateReplacement(gid, TemplateVehicle::Get(tid)); return true; } else { return false; diff --git a/src/tbtr_template_vehicle.h b/src/tbtr_template_vehicle.h index be9e792529..c8630fbb3a 100644 --- a/src/tbtr_template_vehicle.h +++ b/src/tbtr_template_vehicle.h @@ -224,6 +224,8 @@ TemplateReplacement *GetTemplateReplacementByGroupID(GroupID gid); TemplateID GetTemplateIDByGroupID(GroupID gid); TemplateID GetTemplateIDByGroupIDRecursive(GroupID gid); bool IssueTemplateReplacement(GroupID gid, TemplateID tid); +bool ShouldServiceTrainForTemplateReplacement(const Train *t, const TemplateVehicle *tv); +void MarkTrainsUsingTemplateAsPendingTemplateReplacement(const TemplateVehicle *tv); uint DeleteTemplateReplacementsByGroupID(GroupID g_id); diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index eef3b240ef..526015ab99 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2218,6 +2218,7 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, u TraceRestrictRemoveVehicleFromAllSlots(src->index); ClrBit(src->vehicle_flags, VF_HAVE_SLOT); } + ClrBit(src->vehicle_flags, VF_REPLACEMENT_PENDING); OrderBackup::ClearVehicle(src); } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index b689aa2ead..a7d30d404e 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -201,6 +201,7 @@ void VehicleServiceInDepot(Vehicle *v) Ship::From(v)->critical_breakdown_count = 0; } v->vehstatus &= ~VS_AIRCRAFT_BROKEN; + ClrBit(v->vehicle_flags, VF_REPLACEMENT_PENDING); SetWindowDirty(WC_VEHICLE_DETAILS, v->index); // ensure that last service date and reliability are updated do { @@ -231,6 +232,7 @@ bool Vehicle::NeedsServicing() const if (this->vehstatus & (VS_STOPPED | VS_CRASHED)) return false; /* Are we ready for the next service cycle? */ + bool needs_service = true; const Company *c = Company::Get(this->owner); if ((this->ServiceIntervalIsPercent() ? (this->reliability >= this->GetEngine()->reliability * (100 - this->service_interval) / 100) : @@ -238,41 +240,29 @@ bool Vehicle::NeedsServicing() const && !(this->type == VEH_TRAIN && HasBit(Train::From(this)->flags, VRF_CONSIST_BREAKDOWN) && Train::From(this)->ConsistNeedsRepair()) && !(this->type == VEH_ROAD && RoadVehicle::From(this)->critical_breakdown_count > 0) && !(this->type == VEH_SHIP && Ship::From(this)->critical_breakdown_count > 0)) { + needs_service = false; + } + + if (!needs_service && !HasBit(this->vehicle_flags, VF_REPLACEMENT_PENDING)) { return false; } /* If we're servicing anyway, because we have not disabled servicing when * there are no breakdowns or we are playing with breakdowns, bail out. */ - if (!_settings_game.order.no_servicing_if_no_breakdowns || - _settings_game.difficulty.vehicle_breakdowns != 0) { + if (needs_service && (!_settings_game.order.no_servicing_if_no_breakdowns || + _settings_game.difficulty.vehicle_breakdowns != 0)) { return true; } /* Is vehicle old and renewing is enabled */ - if (this->NeedsAutorenewing(c, true)) { + if (needs_service && this->NeedsAutorenewing(c, true)) { return true; } if (this->type == VEH_TRAIN) { - TemplateVehicle *tv = GetTemplateVehicleByGroupIDRecursive(this->group_id); + const TemplateVehicle *tv = GetTemplateVehicleByGroupIDRecursive(this->group_id); if (tv != nullptr) { - if (tv->IsReplaceOldOnly() && !this->NeedsAutorenewing(c, false)) return false; - Money needed_money = c->settings.engine_renew_money; - if (needed_money > c->money) return false; - bool need_replacement = !TrainMatchesTemplate(Train::From(this), tv); - if (need_replacement) { - /* Check money. - * We want 2*(the price of the whole template) without looking at the value of the vehicle(s) we are going to sell, or not need to buy. */ - for (const TemplateVehicle *tv_unit = tv; tv_unit != nullptr; tv_unit = tv_unit->GetNextUnit()) { - if (!HasBit(Engine::Get(tv->engine_type)->company_avail, this->owner)) return false; - needed_money += 2 * Engine::Get(tv->engine_type)->GetCost(); - } - return needed_money <= c->money; - } else if (!TrainMatchesTemplateRefit(Train::From(this), tv) && tv->refit_as_template) { - return true; - } else { - return false; - } + return ShouldServiceTrainForTemplateReplacement(Train::From(this), tv); } } @@ -4196,6 +4186,7 @@ void DumpVehicleFlagsGeneric(const Vehicle *v, T dump, U dump_header) dump('a', "VF_AUTOMATE_TIMETABLE", HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE)); dump('Q', "VF_HAVE_SLOT", HasBit(v->vehicle_flags, VF_HAVE_SLOT)); dump('W', "VF_COND_ORDER_WAIT", HasBit(v->vehicle_flags, VF_COND_ORDER_WAIT)); + dump('r', "VF_REPLACEMENT_PENDING", HasBit(v->vehicle_flags, VF_REPLACEMENT_PENDING)); dump_header("vcf:", "cached_veh_flags:"); dump('l', "VCF_LAST_VISUAL_EFFECT", HasBit(v->vcache.cached_veh_flags, VCF_LAST_VISUAL_EFFECT)); dump('z', "VCF_GV_ZERO_SLOPE_RESIST", HasBit(v->vcache.cached_veh_flags, VCF_GV_ZERO_SLOPE_RESIST)); diff --git a/src/vehicle_base.h b/src/vehicle_base.h index caa88254f2..2f4755917b 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -63,6 +63,7 @@ enum VehicleFlags { VF_AUTOMATE_TIMETABLE = 15, ///< Whether the vehicle should manage the timetable automatically. VF_HAVE_SLOT = 16, ///< Vehicle has 1 or more slots VF_COND_ORDER_WAIT = 17, ///< Vehicle is waiting due to conditional order loop + VF_REPLACEMENT_PENDING = 18, ///< Autoreplace or template replacement is pending, vehicle should visit the depot }; /** Bit numbers used to indicate which of the #NewGRFCache values are valid. */ diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index c8eb2ca88d..b549a246f9 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -947,6 +947,7 @@ CommandCost CmdToggleRefitAsTemplate(TileIndex tile, DoCommandFlag flags, uint32 if (should_execute) { template_vehicle->ToggleRefitAsTemplate(); + MarkTrainsUsingTemplateAsPendingTemplateReplacement(template_vehicle); InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN, 0); } @@ -976,6 +977,7 @@ CommandCost CmdToggleTemplateReplaceOldOnly(TileIndex tile, DoCommandFlag flags, if (should_execute) { template_vehicle->ToggleReplaceOldOnly(); + MarkTrainsUsingTemplateAsPendingTemplateReplacement(template_vehicle); InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN, 0); } @@ -1307,7 +1309,12 @@ CommandCost CmdReplaceTemplateVehicle(TileIndex tile, DoCommandFlag flags, uint3 reindex = true; } } - if (reindex) ReindexTemplateReplacements(); + if (reindex) { + ReindexTemplateReplacements(); + MarkTrainsUsingTemplateAsPendingTemplateReplacement(template_vehicle); + } + } else if (template_vehicle->NumGroupsUsingTemplate() > 0) { + MarkTrainsUsingTemplateAsPendingTemplateReplacement(template_vehicle); } InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN, 0);