diff --git a/src/ai/api/ai_group.cpp b/src/ai/api/ai_group.cpp index 5a95ee7c..c0e91775 100644 --- a/src/ai/api/ai_group.cpp +++ b/src/ai/api/ai_group.cpp @@ -116,7 +116,7 @@ EnforcePrecondition(false, IsValidGroup(group_id) || group_id == GROUP_DEFAULT || group_id == GROUP_ALL); EnforcePrecondition(false, AIEngine::IsBuildable(engine_id_new)); - return AIObject::DoCommand(0, group_id << 16, (engine_id_new << 16) | engine_id_old, CMD_SET_AUTOREPLACE); + return AIObject::DoCommand(0, (group_id << 16) | INVALID_RAILTYPE, (engine_id_new << 16) | engine_id_old, CMD_SET_AUTOREPLACE); } /* static */ EngineID AIGroup::GetEngineReplacement(GroupID group_id, EngineID engine_id) @@ -130,5 +130,5 @@ { EnforcePrecondition(false, IsValidGroup(group_id) || group_id == GROUP_DEFAULT || group_id == GROUP_ALL); - return AIObject::DoCommand(0, group_id << 16, (::INVALID_ENGINE << 16) | engine_id, CMD_SET_AUTOREPLACE); + return AIObject::DoCommand(0, (group_id << 16) | INVALID_RAILTYPE, (::INVALID_ENGINE << 16) | engine_id, CMD_SET_AUTOREPLACE); } diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 67387130..4df43367 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -46,9 +46,10 @@ static bool EnginesHaveCargoInCommon(EngineID engine_a, EngineID engine_b) * @param from Origin engine * @param to Destination engine * @param company Company to check for + * @param rt Target rail type, or INVALID_RAILTYPE for types compatible with origin engine (only for rail engines) * @return true if autoreplace is allowed */ -bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company) +bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company, RailType rt) { assert(Engine::IsValidID(from) && Engine::IsValidID(to)); @@ -59,13 +60,18 @@ bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company) const Engine *e_to = Engine::Get(to); VehicleType type = e_from->type; + if (rt != INVALID_RAILTYPE && type != VEH_TRAIN) return false; + /* check that the new vehicle type is available to the company and its type is the same as the original one */ if (!IsEngineBuildable(to, type, company)) return false; switch (type) { case VEH_TRAIN: { - /* make sure the railtypes are compatible */ - if ((GetRailTypeInfo(e_from->u.rail.railtype)->compatible_railtypes & GetRailTypeInfo(e_to->u.rail.railtype)->compatible_railtypes) == 0) return false; + /* make sure the vehicle is compatible with the railtype */ + bool compatible = (rt == INVALID_RAILTYPE) ? + ((GetRailTypeInfo(e_from->u.rail.railtype)->compatible_railtypes & GetRailTypeInfo(e_to->u.rail.railtype)->compatible_railtypes) != 0) : + HasBit(GetRailTypeInfo(e_to->u.rail.railtype)->compatible_railtypes, rt); + if (!compatible) return false; /* make sure we do not replace wagons with engines or vise versa */ if ((e_from->u.rail.railveh_type == RAILVEH_WAGON) != (e_to->u.rail.railveh_type == RAILVEH_WAGON)) return false; @@ -213,18 +219,27 @@ static CargoID GetNewCargoTypeForReplace(Vehicle *v, EngineID engine_type, bool * Get the EngineID of the replacement for a vehicle * @param v The vehicle to find a replacement for * @param c The vehicle's owner (it's faster to forward the pointer than refinding it) + * @param rt Target railtype (only for rail vehicles) * @param [out] e the EngineID of the replacement. INVALID_ENGINE if no replacement is found * @return Error if the engine to build is not available */ -static CommandCost GetNewEngineType(const Vehicle *v, const Company *c, EngineID &e) +CommandCost GetNewEngineType(const Vehicle *v, const Company *c, RailType rt, EngineID &e) { assert(v->type != VEH_TRAIN || !v->IsArticulatedPart()); e = INVALID_ENGINE; - if (v->type == VEH_TRAIN && Train::From(v)->IsRearDualheaded()) { - /* we build the rear ends of multiheaded trains with the front ones */ - return CommandCost(); + if (v->type == VEH_TRAIN) { + if (Train::From(v)->IsRearDualheaded()) { + /* we build the rear ends of multiheaded trains with the front ones */ + return CommandCost(); + } + + e = EngineReplacementForCompany(c, v->engine_type, v->group_id, rt); + + if (e != INVALID_ENGINE && IsEngineBuildable(e, v->type, _current_company)) { + return CommandCost(); + } } e = EngineReplacementForCompany(c, v->engine_type, v->group_id); @@ -250,16 +265,17 @@ static CommandCost GetNewEngineType(const Vehicle *v, const Company *c, EngineID * @param old_veh A single (articulated/multiheaded) vehicle that shall be replaced. * @param new_vehicle Returns the newly build and refittet vehicle * @param part_of_chain The vehicle is part of a train + * @param rt Target railtype (only for rail vehicles) * @return cost or error */ -static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehicle, bool part_of_chain) +static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehicle, bool part_of_chain, RailType rt) { *new_vehicle = NULL; /* Shall the vehicle be replaced? */ const Company *c = Company::Get(_current_company); EngineID e; - CommandCost cost = GetNewEngineType(old_veh, c, e); + CommandCost cost = GetNewEngineType(old_veh, c, rt, e); if (cost.Failed()) return cost; if (e == INVALID_ENGINE) return CommandCost(); // neither autoreplace is set, nor autorenew is triggered @@ -367,9 +383,10 @@ static CommandCost CopyHeadSpecificThings(Vehicle *old_head, Vehicle *new_head, * @param single_unit vehicle to let autoreplace/renew operator on * @param flags command flags * @param nothing_to_do is set to 'false' when something was done (only valid when not failed) + * @param rt Target railtype * @return cost or error */ -static CommandCost ReplaceFreeUnit(Vehicle **single_unit, DoCommandFlag flags, bool *nothing_to_do) +CommandCost ReplaceFreeUnit(Vehicle **single_unit, DoCommandFlag flags, bool *nothing_to_do, RailType rt) { Train *old_v = Train::From(*single_unit); assert(!old_v->IsArticulatedPart() && !old_v->IsRearDualheaded()); @@ -378,7 +395,7 @@ static CommandCost ReplaceFreeUnit(Vehicle **single_unit, DoCommandFlag flags, b /* Build and refit replacement vehicle */ Vehicle *new_v = NULL; - cost.AddCost(BuildReplacementVehicle(old_v, &new_v, false)); + cost.AddCost(BuildReplacementVehicle(old_v, &new_v, false, rt)); /* Was a new vehicle constructed? */ if (cost.Succeeded() && new_v != NULL) { @@ -417,9 +434,10 @@ static CommandCost ReplaceFreeUnit(Vehicle **single_unit, DoCommandFlag flags, b * @param flags command flags * @param wagon_removal remove wagons when the resulting chain occupies more tiles than the old did * @param nothing_to_do is set to 'false' when something was done (only valid when not failed) + * @param rt Target railtype (only for rail vehicles) * @return cost or error */ -static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon_removal, bool *nothing_to_do) +CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon_removal, bool *nothing_to_do, RailType rt) { Vehicle *old_head = *chain; assert(old_head->IsPrimaryVehicle()); @@ -445,7 +463,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon assert(i < num_units); old_vehs[i] = w; - CommandCost ret = BuildReplacementVehicle(old_vehs[i], (Vehicle**)&new_vehs[i], true); + CommandCost ret = BuildReplacementVehicle(old_vehs[i], (Vehicle**)&new_vehs[i], true, rt); cost.AddCost(ret); if (cost.Failed()) break; @@ -603,7 +621,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon } else { /* Build and refit replacement vehicle */ Vehicle *new_head = NULL; - cost.AddCost(BuildReplacementVehicle(old_head, &new_head, true)); + cost.AddCost(BuildReplacementVehicle(old_head, &new_head, true, rt)); /* Was a new vehicle constructed? */ if (cost.Succeeded() && new_head != NULL) { @@ -654,12 +672,14 @@ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1 if (!v->IsInDepot()) return CMD_ERROR; if (v->vehstatus & VS_CRASHED) return CMD_ERROR; + RailType rt = INVALID_RAILTYPE; bool free_wagon = false; if (v->type == VEH_TRAIN) { Train *t = Train::From(v); if (t->IsArticulatedPart() || t->IsRearDualheaded()) return CMD_ERROR; free_wagon = !t->IsFrontEngine(); if (free_wagon && t->First()->IsFrontEngine()) return CMD_ERROR; + rt = GetRailType(v->tile); } else { if (!v->IsPrimaryVehicle()) return CMD_ERROR; } @@ -672,7 +692,7 @@ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1 bool any_replacements = false; while (w != NULL) { EngineID e; - CommandCost cost = GetNewEngineType(w, c, e); + CommandCost cost = GetNewEngineType(w, c, rt, e); if (cost.Failed()) return cost; any_replacements |= (e != INVALID_ENGINE); w = (!free_wagon && w->type == VEH_TRAIN ? Train::From(w)->GetNextUnit() : NULL); @@ -696,18 +716,18 @@ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1 SavedRandomSeeds saved_seeds; SaveRandomSeeds(&saved_seeds); if (free_wagon) { - cost.AddCost(ReplaceFreeUnit(&v, flags & ~DC_EXEC, ¬hing_to_do)); + cost.AddCost(ReplaceFreeUnit(&v, flags & ~DC_EXEC, ¬hing_to_do, rt)); } else { - cost.AddCost(ReplaceChain(&v, flags & ~DC_EXEC, wagon_removal, ¬hing_to_do)); + cost.AddCost(ReplaceChain(&v, flags & ~DC_EXEC, wagon_removal, ¬hing_to_do, rt)); } RestoreRandomSeeds(saved_seeds); if (cost.Succeeded() && (flags & DC_EXEC) != 0) { CommandCost ret; if (free_wagon) { - ret = ReplaceFreeUnit(&v, flags, ¬hing_to_do); + ret = ReplaceFreeUnit(&v, flags, ¬hing_to_do, rt); } else { - ret = ReplaceChain(&v, flags, wagon_removal, ¬hing_to_do); + ret = ReplaceChain(&v, flags, wagon_removal, ¬hing_to_do, rt); } assert(ret.Succeeded() && ret.GetCost() == cost.GetCost()); } @@ -725,6 +745,7 @@ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1 * @param tile unused * @param flags operation to perform * @param p1 packed data + * - bits 0-7 = target railtype for rail engines * - bits 16-31 = engine group * @param p2 packed data * - bits 0-15 = old engine type @@ -740,6 +761,7 @@ CommandCost CmdSetAutoReplace(TileIndex tile, DoCommandFlag flags, uint32 p1, ui EngineID old_engine_type = GB(p2, 0, 16); EngineID new_engine_type = GB(p2, 16, 16); GroupID id_g = GB(p1, 16, 16); + RailType rt = (RailType)GB(p1, 0, 8); CommandCost cost; if (Group::IsValidID(id_g) ? Group::Get(id_g)->owner != _current_company : !IsAllGroupID(id_g) && !IsDefaultGroupID(id_g)) return CMD_ERROR; @@ -747,11 +769,11 @@ CommandCost CmdSetAutoReplace(TileIndex tile, DoCommandFlag flags, uint32 p1, ui if (new_engine_type != INVALID_ENGINE) { if (!Engine::IsValidID(new_engine_type)) return CMD_ERROR; - if (!CheckAutoreplaceValidity(old_engine_type, new_engine_type, _current_company)) return CMD_ERROR; + if (!CheckAutoreplaceValidity(old_engine_type, new_engine_type, _current_company, rt)) return CMD_ERROR; - cost = AddEngineReplacementForCompany(c, old_engine_type, new_engine_type, id_g, flags); + cost = AddEngineReplacementForCompany(c, old_engine_type, new_engine_type, id_g, flags, rt); } else { - cost = RemoveEngineReplacementForCompany(c, old_engine_type, id_g, flags); + cost = RemoveEngineReplacementForCompany(c, old_engine_type, id_g, flags, rt); } if (flags & DC_EXEC) GroupStatistics::UpdateAutoreplace(_current_company); diff --git a/src/autoreplace_func.h b/src/autoreplace_func.h index fdb88984..3698173e 100644 --- a/src/autoreplace_func.h +++ b/src/autoreplace_func.h @@ -15,6 +15,7 @@ #include "command_type.h" #include "company_base.h" #include "engine_type.h" +#include "engine_base.h" #include "group_type.h" void RemoveAllEngineReplacement(EngineRenewList *erl); @@ -23,12 +24,36 @@ CommandCost AddEngineReplacement(EngineRenewList *erl, EngineID old_engine, Engi CommandCost RemoveEngineReplacement(EngineRenewList *erl, EngineID engine, GroupID group, DoCommandFlag flags); /** + * Get the applicable engine renew list for the given company, engine id and rail type + * @param c company + * @param engine source engine type + * @param rt target railtype, or INVALID_RAILTYPE to use the default list (only for rail engines) + * @return The corresponding engine renew list + */ +static inline EngineRenewList *GetEngineRenewList(Company *c, EngineID engine, RailType rt) +{ + if (rt == INVALID_RAILTYPE) return &c->engine_renew_list; + assert(Engine::Get(engine)->type == VEH_TRAIN); + return &c->rail_renew_list[rt]; +} + +static inline const EngineRenewList *GetEngineRenewList(const Company *c, EngineID engine, RailType rt) +{ + if (rt == INVALID_RAILTYPE) return &c->engine_renew_list; + assert(Engine::Get(engine)->type == VEH_TRAIN); + return &c->rail_renew_list[rt]; +} + +/** * Remove all engine replacement settings for the given company. * @param c the company. */ static inline void RemoveAllEngineReplacementForCompany(Company *c) { RemoveAllEngineReplacement(&c->engine_renew_list); + for (RailType rt = RAILTYPE_BEGIN; rt < RAILTYPE_END; rt++) { + RemoveAllEngineReplacement(&c->rail_renew_list[rt]); + } } /** @@ -36,12 +61,14 @@ static inline void RemoveAllEngineReplacementForCompany(Company *c) * @param c company. * @param engine Engine type. * @param group The group related to this replacement. + * @param rt target railtype, or INVALID_RAILTYPE to use the default list (only for rail engines) * @return The engine type to replace with, or INVALID_ENGINE if no * replacement is in the list. */ -static inline EngineID EngineReplacementForCompany(const Company *c, EngineID engine, GroupID group) +static inline EngineID EngineReplacementForCompany(const Company *c, EngineID engine, GroupID group, RailType rt = INVALID_RAILTYPE) { - return EngineReplacement(c->engine_renew_list, engine, group); + const EngineRenewList *erl = GetEngineRenewList(c, engine, rt); + return EngineReplacement(*erl, engine, group); } /** @@ -49,11 +76,12 @@ static inline EngineID EngineReplacementForCompany(const Company *c, EngineID en * @param c Company. * @param engine Engine type to be replaced. * @param group The group related to this replacement. + * @param rt target railtype, or INVALID_RAILTYPE to use the default list (only for rail engines) * @return true if a replacement was set up, false otherwise. */ -static inline bool EngineHasReplacementForCompany(const Company *c, EngineID engine, GroupID group) +static inline bool EngineHasReplacementForCompany(const Company *c, EngineID engine, GroupID group, RailType rt = INVALID_RAILTYPE) { - return EngineReplacementForCompany(c, engine, group) != INVALID_ENGINE; + return EngineReplacementForCompany(c, engine, group, rt) != INVALID_ENGINE; } /** @@ -63,11 +91,13 @@ static inline bool EngineHasReplacementForCompany(const Company *c, EngineID eng * @param new_engine The replacement engine type. * @param group The group related to this replacement. * @param flags The calling command flags. + * @param rt target railtype, or INVALID_RAILTYPE to use the default list (only for rail engines) * @return 0 on success, CMD_ERROR on failure. */ -static inline CommandCost AddEngineReplacementForCompany(Company *c, EngineID old_engine, EngineID new_engine, GroupID group, DoCommandFlag flags) +static inline CommandCost AddEngineReplacementForCompany(Company *c, EngineID old_engine, EngineID new_engine, GroupID group, DoCommandFlag flags, RailType rt) { - return AddEngineReplacement(&c->engine_renew_list, old_engine, new_engine, group, flags); + EngineRenewList *erl = GetEngineRenewList(c, old_engine, rt); + return AddEngineReplacement(erl, old_engine, new_engine, group, flags); } /** @@ -76,13 +106,15 @@ static inline CommandCost AddEngineReplacementForCompany(Company *c, EngineID ol * @param engine The original engine type. * @param group The group related to this replacement. * @param flags The calling command flags. + * @param rt target railtype, or INVALID_RAILTYPE to use the default list (only for rail engines) * @return 0 on success, CMD_ERROR on failure. */ -static inline CommandCost RemoveEngineReplacementForCompany(Company *c, EngineID engine, GroupID group, DoCommandFlag flags) +static inline CommandCost RemoveEngineReplacementForCompany(Company *c, EngineID engine, GroupID group, DoCommandFlag flags, RailType rt) { - return RemoveEngineReplacement(&c->engine_renew_list, engine, group, flags); + EngineRenewList *erl = GetEngineRenewList(c, engine, rt); + return RemoveEngineReplacement(erl, engine, group, flags); } -bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company); +bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company, RailType rt = INVALID_RAILTYPE); #endif /* AUTOREPLACE_FUNC_H */ diff --git a/src/autoreplace_gui.cpp b/src/autoreplace_gui.cpp index 6f29b6de..111aabae 100644 --- a/src/autoreplace_gui.cpp +++ b/src/autoreplace_gui.cpp @@ -49,11 +49,12 @@ enum ReplaceVehicleWindowWidgets { RVW_WIDGET_STOP_REPLACE, /* Train only widgets. */ + RVW_WIDGET_REPLACING, RVW_WIDGET_TRAIN_ENGINEWAGON_TOGGLE, - RVW_WIDGET_TRAIN_FLUFF_LEFT, - RVW_WIDGET_TRAIN_RAILTYPE_DROPDOWN, - RVW_WIDGET_TRAIN_FLUFF_RIGHT, + RVW_WIDGET_REPLACEMENT, RVW_WIDGET_TRAIN_WAGONREMOVE_TOGGLE, + RVW_WIDGET_TRAIN_LEFT_RAILTYPE_DROPDOWN, + RVW_WIDGET_TRAIN_RIGHT_RAILTYPE_DROPDOWN, }; static int CDECL EngineNumberSorter(const EngineID *a, const EngineID *b) @@ -102,7 +103,8 @@ class ReplaceVehicleWindow : public Window { bool reset_sel_engine; ///< Also reset #sel_engine while updating left and/or right (#update_left and/or #update_right) and no valid engine selected. GroupID sel_group; ///< Group selected to replace. int details_height; ///< Minimal needed height of the details panels (found so far). - RailType sel_railtype; ///< Type of rail tracks selected. + RailType src_railtype; ///< Type of rail vehicles selected. + RailType tgt_railtype; ///< Type of rail tracks selected. Scrollbar *vscroll[2]; /** @@ -119,9 +121,14 @@ class ReplaceVehicleWindow : public Window { /* Ensure that the wagon/engine selection fits the engine. */ if ((rvi->railveh_type == RAILVEH_WAGON) == show_engines) return false; - if (draw_left && show_engines) { - /* Ensure that the railtype is specific to the selected one */ - if (rvi->railtype != this->sel_railtype) return false; + if (draw_left) { + if (show_engines) { + /* Ensure that the railtype is specific to the selected one */ + return rvi->railtype == this->src_railtype; + } else { + /* Ensure that the railtype is compatible with the selected one */ + return HasBit(GetRailTypeInfo(rvi->railtype)->compatible_railtypes, this->src_railtype); + } } return true; } @@ -146,12 +153,21 @@ class ReplaceVehicleWindow : public Window { if (type == VEH_TRAIN && !this->GenerateReplaceRailList(eid, draw_left, this->replace_engines)) continue; // special rules for trains if (draw_left) { - const uint num_engines = GetGroupNumEngines(_local_company, this->sel_group, eid); - /* Skip drawing the engines we don't have any of and haven't set for replacement */ - if (num_engines == 0 && EngineReplacementForCompany(Company::Get(_local_company), eid, this->sel_group) == INVALID_ENGINE) continue; + if (GetGroupNumEngines(_local_company, this->sel_group, eid) == 0) { + bool has_replacement = EngineHasReplacementForCompany(Company::Get(_local_company), eid, this->sel_group); + if (!has_replacement && (type == VEH_TRAIN)) { + for (RailType rt = RAILTYPE_BEGIN; rt < RAILTYPE_END; rt++) { + if (EngineHasReplacementForCompany(Company::Get(_local_company), eid, this->sel_group, rt)) { + has_replacement = true; + break; + } + } + } + if (!has_replacement) continue; + } } else { - if (!CheckAutoreplaceValidity(this->sel_engine[0], eid, _local_company)) continue; + if (!CheckAutoreplaceValidity(this->sel_engine[0], eid, _local_company, this->tgt_railtype)) continue; } *list->Append() = eid; @@ -182,11 +198,11 @@ class ReplaceVehicleWindow : public Window { this->engines[1].Clear(); this->sel_engine[1] = INVALID_ENGINE; } else { + if (this->reset_sel_engine) { + this->sel_engine[1] = EngineReplacementForCompany(Company::Get(_local_company), this->sel_engine[0], this->sel_group, this->tgt_railtype); + } this->GenerateReplaceVehList(false); this->vscroll[1]->SetCount(this->engines[1].Length()); - if (this->reset_sel_engine && this->sel_engine[1] == INVALID_ENGINE && this->engines[1].Length() != 0) { - this->sel_engine[1] = this->engines[1][0]; - } } } /* Reset the flags about needed updates */ @@ -210,9 +226,9 @@ public: type_count[e->u.rail.railtype] += GetGroupNumEngines(_local_company, id_g, e->index); } - this->sel_railtype = RAILTYPE_BEGIN; - for (RailType rt = RAILTYPE_BEGIN; rt < RAILTYPE_END; rt++) { - if (type_count[this->sel_railtype] < type_count[rt]) this->sel_railtype = rt; + this->src_railtype = RAILTYPE_BEGIN; + for (RailType rt = (RailType)(RAILTYPE_BEGIN + 1); rt < RAILTYPE_END; rt++) { + if (type_count[this->src_railtype] < type_count[rt]) this->src_railtype = rt; } } @@ -223,6 +239,7 @@ public: this->details_height = ((vehicletype == VEH_TRAIN) ? 10 : 9) * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM; this->sel_engine[0] = INVALID_ENGINE; this->sel_engine[1] = INVALID_ENGINE; + this->tgt_railtype = INVALID_RAILTYPE; this->CreateNestedTree(desc); this->vscroll[0] = this->GetScrollbar(RVW_WIDGET_LEFT_SCROLLBAR); @@ -282,7 +299,14 @@ public: break; } - case RVW_WIDGET_TRAIN_RAILTYPE_DROPDOWN: { + case RVW_WIDGET_TRAIN_RIGHT_RAILTYPE_DROPDOWN: { + Dimension d = GetStringBoundingBox(STR_REPLACE_DEFAULT_RAILTYPE); + d.width += padding.width; + d.height += padding.height; + *size = maxdim(*size, d); + } + /* fall through */ + case RVW_WIDGET_TRAIN_LEFT_RAILTYPE_DROPDOWN: { Dimension d = {0, 0}; for (RailType rt = RAILTYPE_BEGIN; rt != RAILTYPE_END; rt++) { const RailtypeInfo *rti = GetRailTypeInfo(rt); @@ -321,19 +345,22 @@ public: { switch (widget) { case RVW_WIDGET_INFO_TAB: { + DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_REPLACE_INFO_TAB, TC_FROMSTRING, SA_HOR_CENTER); + const Company *c = Company::Get(_local_company); if (this->sel_engine[0] != INVALID_ENGINE) { - if (!EngineHasReplacementForCompany(c, this->sel_engine[0], this->sel_group)) { + EngineID eid = EngineReplacementForCompany(c, this->sel_engine[0], this->sel_group, this->tgt_railtype); + if (eid == INVALID_ENGINE) { SetDParam(0, STR_REPLACE_NOT_REPLACING); } else { SetDParam(0, STR_ENGINE_NAME); - SetDParam(1, EngineReplacementForCompany(c, this->sel_engine[0], this->sel_group)); + SetDParam(1, eid); } } else { SetDParam(0, STR_REPLACE_NOT_REPLACING_VEHICLE_SELECTED); } - DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_BLACK_STRING, TC_FROMSTRING, SA_HOR_CENTER); + DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMERECT_TOP + 12, STR_BLACK_STRING, TC_FROMSTRING, SA_HOR_CENTER); break; } @@ -348,6 +375,14 @@ public: &this->engines[side], start, end, this->sel_engine[side], side == 0, this->sel_group); break; } + + case RVW_WIDGET_REPLACING: + DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_REPLACE_REPLACING, TC_FROMSTRING, SA_LEFT); + break; + + case RVW_WIDGET_REPLACEMENT: + DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_REPLACE_REPLACEMENT, TC_FROMSTRING, SA_LEFT); + break; } } @@ -364,26 +399,24 @@ public: this->SetWidgetDisabledState(RVW_WIDGET_START_REPLACE, this->sel_engine[0] == INVALID_ENGINE || this->sel_engine[1] == INVALID_ENGINE || - EngineReplacementForCompany(c, this->sel_engine[1], this->sel_group) != INVALID_ENGINE || - EngineReplacementForCompany(c, this->sel_engine[0], this->sel_group) == this->sel_engine[1]); + EngineHasReplacementForCompany(c, this->sel_engine[1], this->sel_group, this->tgt_railtype) || + EngineReplacementForCompany(c, this->sel_engine[0], this->sel_group, this->tgt_railtype) == this->sel_engine[1]); /* Disable the "Stop Replacing" button if: * The left engines list (existing vehicle) is empty * or The selected vehicle has no replacement set up */ this->SetWidgetDisabledState(RVW_WIDGET_STOP_REPLACE, this->sel_engine[0] == INVALID_ENGINE || - !EngineHasReplacementForCompany(c, this->sel_engine[0], this->sel_group)); + !EngineHasReplacementForCompany(c, this->sel_engine[0], this->sel_group, this->tgt_railtype)); /* now the actual drawing of the window itself takes place */ SetDParam(0, STR_REPLACE_VEHICLE_TRAIN + this->window_number); if (this->window_number == VEH_TRAIN) { - /* sets the colour of that art thing */ - this->GetWidget(RVW_WIDGET_TRAIN_FLUFF_LEFT)->colour = _company_colours[_local_company]; - this->GetWidget(RVW_WIDGET_TRAIN_FLUFF_RIGHT)->colour = _company_colours[_local_company]; - /* Show the selected railtype in the pulldown menu */ - this->GetWidget(RVW_WIDGET_TRAIN_RAILTYPE_DROPDOWN)->widget_data = GetRailTypeInfo(sel_railtype)->strings.replace_text; + this->GetWidget(RVW_WIDGET_TRAIN_LEFT_RAILTYPE_DROPDOWN)->widget_data = GetRailTypeInfo(this->src_railtype)->strings.replace_text; + this->GetWidget(RVW_WIDGET_TRAIN_RIGHT_RAILTYPE_DROPDOWN)->widget_data = + this->tgt_railtype == INVALID_RAILTYPE ? STR_REPLACE_DEFAULT_RAILTYPE : GetRailTypeInfo(this->tgt_railtype)->strings.replace_text; } this->DrawWidgets(); @@ -417,10 +450,18 @@ public: this->SetDirty(); break; - case RVW_WIDGET_TRAIN_RAILTYPE_DROPDOWN: // Railtype selection dropdown menu - ShowDropDownList(this, GetRailTypeDropDownList(true), sel_railtype, RVW_WIDGET_TRAIN_RAILTYPE_DROPDOWN); + case RVW_WIDGET_TRAIN_LEFT_RAILTYPE_DROPDOWN: // Source railtype selection dropdown menu + ShowDropDownList(this, GetRailTypeDropDownList(true), this->src_railtype, RVW_WIDGET_TRAIN_LEFT_RAILTYPE_DROPDOWN); break; + case RVW_WIDGET_TRAIN_RIGHT_RAILTYPE_DROPDOWN: { // Target railtype selection dropdown menu + DropDownList *list = GetRailTypeDropDownList(true); + DropDownListStringItem *item = new DropDownListStringItem(STR_REPLACE_DEFAULT_RAILTYPE, INVALID_RAILTYPE, false); + list->push_back(item); + ShowDropDownList(this, list, this->tgt_railtype, RVW_WIDGET_TRAIN_RIGHT_RAILTYPE_DROPDOWN); + break; + } + case RVW_WIDGET_TRAIN_WAGONREMOVE_TOGGLE: // toggle renew_keep_length DoCommandP(0, GetCompanySettingIndex("company.renew_keep_length"), Company::Get(_local_company)->settings.renew_keep_length ? 0 : 1, CMD_CHANGE_COMPANY_SETTING); break; @@ -428,14 +469,14 @@ public: case RVW_WIDGET_START_REPLACE: { // Start replacing EngineID veh_from = this->sel_engine[0]; EngineID veh_to = this->sel_engine[1]; - DoCommandP(0, this->sel_group << 16, veh_from + (veh_to << 16), CMD_SET_AUTOREPLACE); + DoCommandP(0, this->tgt_railtype + (this->sel_group << 16), veh_from + (veh_to << 16), CMD_SET_AUTOREPLACE); this->SetDirty(); break; } case RVW_WIDGET_STOP_REPLACE: { // Stop replacing EngineID veh_from = this->sel_engine[0]; - DoCommandP(0, this->sel_group << 16, veh_from + (INVALID_ENGINE << 16), CMD_SET_AUTOREPLACE); + DoCommandP(0, this->tgt_railtype + (this->sel_group << 16), veh_from + (INVALID_ENGINE << 16), CMD_SET_AUTOREPLACE); this->SetDirty(); break; } @@ -467,13 +508,19 @@ public: virtual void OnDropdownSelect(int widget, int index) { RailType temp = (RailType)index; - if (temp == sel_railtype) return; // we didn't select a new one. No need to change anything - sel_railtype = temp; - /* Reset scrollbar positions */ - this->vscroll[0]->SetPosition(0); + + if (widget == RVW_WIDGET_TRAIN_LEFT_RAILTYPE_DROPDOWN) { + if (temp == this->src_railtype) return; + this->src_railtype = temp; + this->vscroll[0]->SetPosition(0); + this->engines[0].ForceRebuild(); + } else { + if (temp == this->tgt_railtype) return; + this->tgt_railtype = temp; + } + + /* target list is rebuilt unconditionally */ this->vscroll[1]->SetPosition(0); - /* Rebuild the lists */ - this->engines[0].ForceRebuild(); this->engines[1].ForceRebuild(); this->reset_sel_engine = true; this->SetDirty(); @@ -512,6 +559,20 @@ static const NWidgetPart _nested_replace_rail_vehicle_widgets[] = { NWidget(WWT_STICKYBOX, COLOUR_GREY), EndContainer(), NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY, RVW_WIDGET_REPLACING), SetMinimalSize(90, 12), SetResize(1, 0), EndContainer(), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RVW_WIDGET_TRAIN_ENGINEWAGON_TOGGLE), SetMinimalSize(138, 12), SetDataTip(STR_REPLACE_ENGINE_WAGON_SELECT, STR_REPLACE_ENGINE_WAGON_SELECT_HELP), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY, RVW_WIDGET_REPLACEMENT), SetMinimalSize(90, 12), SetResize(1, 0), EndContainer(), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RVW_WIDGET_TRAIN_WAGONREMOVE_TOGGLE), SetMinimalSize(138, 12), SetDataTip(STR_REPLACE_REMOVE_WAGON, STR_REPLACE_REMOVE_WAGON_HELP), + EndContainer(), + EndContainer(), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), + NWidget(WWT_DROPDOWN, COLOUR_GREY, RVW_WIDGET_TRAIN_LEFT_RAILTYPE_DROPDOWN), SetMinimalSize(228, 12), SetDataTip(0x0, STR_REPLACE_HELP_RAILTYPE), SetResize(1, 0), + NWidget(WWT_DROPDOWN, COLOUR_GREY, RVW_WIDGET_TRAIN_RIGHT_RAILTYPE_DROPDOWN), SetMinimalSize(228, 12), SetDataTip(0x0, STR_REPLACE_HELP_RAILTYPE), SetResize(1, 0), + EndContainer(), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), NWidget(WWT_MATRIX, COLOUR_GREY, RVW_WIDGET_LEFT_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetDataTip(0x1, STR_REPLACE_HELP_LEFT_ARRAY), SetResize(1, 1), SetScrollbar(RVW_WIDGET_LEFT_SCROLLBAR), NWidget(NWID_VSCROLLBAR, COLOUR_GREY, RVW_WIDGET_LEFT_SCROLLBAR), NWidget(WWT_MATRIX, COLOUR_GREY, RVW_WIDGET_RIGHT_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetDataTip(0x1, STR_REPLACE_HELP_RIGHT_ARRAY), SetResize(1, 1), SetScrollbar(RVW_WIDGET_RIGHT_SCROLLBAR), @@ -521,19 +582,15 @@ static const NWidgetPart _nested_replace_rail_vehicle_widgets[] = { NWidget(WWT_PANEL, COLOUR_GREY, RVW_WIDGET_LEFT_DETAILS), SetMinimalSize(228, 102), SetResize(1, 0), EndContainer(), NWidget(WWT_PANEL, COLOUR_GREY, RVW_WIDGET_RIGHT_DETAILS), SetMinimalSize(228, 102), SetResize(1, 0), EndContainer(), EndContainer(), - NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RVW_WIDGET_START_REPLACE), SetMinimalSize(139, 12), SetDataTip(STR_REPLACE_VEHICLES_START, STR_REPLACE_HELP_START_BUTTON), - NWidget(WWT_PANEL, COLOUR_GREY, RVW_WIDGET_INFO_TAB), SetMinimalSize(167, 12), SetDataTip(0x0, STR_REPLACE_HELP_REPLACE_INFO_TAB), SetResize(1, 0), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), + NWidget(WWT_PANEL, COLOUR_GREY, RVW_WIDGET_INFO_TAB), SetMinimalSize(228, 12), SetDataTip(0x0, STR_REPLACE_HELP_REPLACE_INFO_TAB), SetResize(1, 0), EndContainer(), + NWidget(NWID_VERTICAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RVW_WIDGET_START_REPLACE), SetMinimalSize(228, 12), SetDataTip(STR_REPLACE_VEHICLES_START, STR_REPLACE_HELP_START_BUTTON), SetResize(1, 0), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RVW_WIDGET_STOP_REPLACE), SetMinimalSize(216, 12), SetDataTip(STR_REPLACE_VEHICLES_STOP, STR_REPLACE_HELP_STOP_BUTTON), SetResize(1, 0), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), EndContainer(), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RVW_WIDGET_STOP_REPLACE), SetMinimalSize(150, 12), SetDataTip(STR_REPLACE_VEHICLES_STOP, STR_REPLACE_HELP_STOP_BUTTON), - EndContainer(), - NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RVW_WIDGET_TRAIN_ENGINEWAGON_TOGGLE), SetMinimalSize(139, 12), SetDataTip(STR_REPLACE_ENGINE_WAGON_SELECT, STR_REPLACE_ENGINE_WAGON_SELECT_HELP), - NWidget(WWT_PANEL, COLOUR_GREY, RVW_WIDGET_TRAIN_FLUFF_LEFT), SetMinimalSize(15, 12), EndContainer(), - NWidget(WWT_DROPDOWN, COLOUR_GREY, RVW_WIDGET_TRAIN_RAILTYPE_DROPDOWN), SetMinimalSize(136, 12), SetDataTip(0x0, STR_REPLACE_HELP_RAILTYPE), SetResize(1, 0), - NWidget(WWT_PANEL, COLOUR_GREY, RVW_WIDGET_TRAIN_FLUFF_RIGHT), SetMinimalSize(16, 12), EndContainer(), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RVW_WIDGET_TRAIN_WAGONREMOVE_TOGGLE), SetMinimalSize(138, 12), SetDataTip(STR_REPLACE_REMOVE_WAGON, STR_REPLACE_REMOVE_WAGON_HELP), - NWidget(WWT_RESIZEBOX, COLOUR_GREY), EndContainer(), }; @@ -547,7 +604,7 @@ static const WindowDesc _replace_rail_vehicle_desc( static const NWidgetPart _nested_replace_vehicle_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_CLOSEBOX, COLOUR_GREY), - NWidget(WWT_CAPTION, COLOUR_GREY, RVW_WIDGET_CAPTION), SetMinimalSize(433, 14), SetDataTip(STR_REPLACE_VEHICLES_WHITE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_CAPTION, COLOUR_GREY, RVW_WIDGET_CAPTION), SetDataTip(STR_REPLACE_VEHICLES_WHITE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), NWidget(WWT_SHADEBOX, COLOUR_GREY), NWidget(WWT_STICKYBOX, COLOUR_GREY), EndContainer(), @@ -561,11 +618,15 @@ static const NWidgetPart _nested_replace_vehicle_widgets[] = { NWidget(WWT_PANEL, COLOUR_GREY, RVW_WIDGET_LEFT_DETAILS), SetMinimalSize(228, 92), SetResize(1, 0), EndContainer(), NWidget(WWT_PANEL, COLOUR_GREY, RVW_WIDGET_RIGHT_DETAILS), SetMinimalSize(228, 92), SetResize(1, 0), EndContainer(), EndContainer(), - NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RVW_WIDGET_START_REPLACE), SetMinimalSize(139, 12), SetDataTip(STR_REPLACE_VEHICLES_START, STR_REPLACE_HELP_START_BUTTON), - NWidget(WWT_PANEL, COLOUR_GREY, RVW_WIDGET_INFO_TAB), SetMinimalSize(167, 12), SetDataTip(0x0, STR_REPLACE_HELP_REPLACE_INFO_TAB), SetResize(1, 0), EndContainer(), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RVW_WIDGET_STOP_REPLACE), SetMinimalSize(138, 12), SetDataTip(STR_REPLACE_VEHICLES_STOP, STR_REPLACE_HELP_STOP_BUTTON), - NWidget(WWT_RESIZEBOX, COLOUR_GREY), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), + NWidget(WWT_PANEL, COLOUR_GREY, RVW_WIDGET_INFO_TAB), SetMinimalSize(228, 12), SetDataTip(0x0, STR_REPLACE_HELP_REPLACE_INFO_TAB), SetResize(1, 0), EndContainer(), + NWidget(NWID_VERTICAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RVW_WIDGET_START_REPLACE), SetMinimalSize(228, 12), SetDataTip(STR_REPLACE_VEHICLES_START, STR_REPLACE_HELP_START_BUTTON), SetResize(1, 0), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RVW_WIDGET_STOP_REPLACE), SetMinimalSize(216, 12), SetDataTip(STR_REPLACE_VEHICLES_STOP, STR_REPLACE_HELP_STOP_BUTTON), SetResize(1, 0), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), + EndContainer(), EndContainer(), }; diff --git a/src/company_base.h b/src/company_base.h index bf655ea3..57ba5088 100644 --- a/src/company_base.h +++ b/src/company_base.h @@ -104,6 +104,7 @@ struct Company : CompanyPool::PoolItem<&_company_pool>, CompanyProperties { class AIInfo *ai_info; EngineRenewList engine_renew_list; ///< Engine renewals of this company. + EngineRenewList rail_renew_list[RAILTYPE_END]; ///< Rail engine renewals of this company. CompanySettings settings; ///< settings specific for each company GroupStatistics group_all[VEH_COMPANY_END]; ///< NOSAVE: Statistics for the ALL_GROUP group. GroupStatistics group_default[VEH_COMPANY_END]; ///< NOSAVE: Statistics for the DEFAULT_GROUP group. diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp index 9ae39025..5f6b2e85 100644 --- a/src/group_cmd.cpp +++ b/src/group_cmd.cpp @@ -326,12 +326,16 @@ CommandCost CmdDeleteGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 /* If we set an autoreplace for the group we delete, remove it. */ if (_current_company < MAX_COMPANIES) { - Company *c; + Company *c = Company::Get(_current_company); EngineRenew *er; - c = Company::Get(_current_company); FOR_ALL_ENGINE_RENEWS(er) { - if (er->group_id == g->index) RemoveEngineReplacementForCompany(c, er->from, g->index, flags); + if (er->group_id == g->index) { + RemoveEngineReplacement(&c->engine_renew_list, er->from, g->index, flags); + for (RailType rt = RAILTYPE_BEGIN; rt < RAILTYPE_END; rt++) { + RemoveEngineReplacement(&c->rail_renew_list[rt], er->from, g->index, flags); + } + } } } diff --git a/src/lang/english.txt b/src/lang/english.txt index e2b778c3..ea9bca69 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2983,6 +2983,7 @@ STR_REPLACE_VEHICLE_AIRCRAFT :Aircraft STR_REPLACE_HELP_LEFT_ARRAY :{BLACK}Select the engine type to replace STR_REPLACE_HELP_RIGHT_ARRAY :{BLACK}Select the new engine type you would like to use in place of the left selected engine type +STR_REPLACE_INFO_TAB :{BLACK}Current replacement: STR_REPLACE_VEHICLES_START :{BLACK}Start Replacing Vehicles STR_REPLACE_HELP_START_BUTTON :{BLACK}Press to begin replacement of the left selected engine type with the right selected engine type STR_REPLACE_NOT_REPLACING :{BLACK}Not replacing @@ -2990,7 +2991,11 @@ STR_REPLACE_NOT_REPLACING_VEHICLE_SELECTED :{BLACK}No vehic STR_REPLACE_VEHICLES_STOP :{BLACK}Stop Replacing Vehicles STR_REPLACE_HELP_STOP_BUTTON :{BLACK}Press to stop the replacement of the engine type selected on the left -STR_REPLACE_ENGINE_WAGON_SELECT :{BLACK}Replacing: {ORANGE}{STRING} +STR_REPLACE_REPLACING :{BLACK}Replacing: +STR_REPLACE_REPLACEMENT :{BLACK}Replacement: +STR_REPLACE_DEFAULT_RAILTYPE :Default replacement + +STR_REPLACE_ENGINE_WAGON_SELECT :{BLACK}{STRING} STR_REPLACE_ENGINE_WAGON_SELECT_HELP :{BLACK}Switch between engine and wagon replacement windows STR_REPLACE_ENGINES :Engines STR_REPLACE_WAGONS :Wagons diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 35895c38..ffcd6e80 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -31,6 +31,11 @@ #include "company_base.h" #include "core/backup_type.hpp" #include "date_func.h" +#include "aircraft.h" +#include "autoreplace_func.h" +#include "engine_func.h" +#include "core/random_func.hpp" +#include "vehiclelist.h" #include "table/strings.h" #include "table/railtypes.h" @@ -1395,6 +1400,91 @@ static Vehicle *UpdateTrainPowerProc(Vehicle *v, void *data) return NULL; } +extern CommandCost GetNewEngineType(const Vehicle *v, const Company *c, RailType rt, EngineID &e); +extern CommandCost ReplaceFreeUnit(Vehicle **single_unit, DoCommandFlag flags, bool *nothing_to_do, RailType rt); +extern CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon_removal, bool *nothing_to_do, RailType rt); +extern Vehicle *VehicleFromPos(TileIndex tile, void *data, VehicleFromPosProc *proc, bool find_first); + +struct DepotConversionData { + const Company *c; + DoCommandFlag flags; + CommandCost cost; + uint z; + RailType source, target; + bool compatible; +}; + +/** + * Callback for depot conversion (check) + * @param v Vehicle to examine. + * @param data Pointer to conversion data. + * @return \a v if the vehicle prevents conversion, else \c NULL. + */ +static Vehicle *DepotConversionCheck(Vehicle *v, void *data) +{ + DepotConversionData *dcd = (DepotConversionData*)data; + + if (v->type == VEH_DISASTER || (v->type == VEH_AIRCRAFT && v->subtype == AIR_SHADOW)) return NULL; + if (v->z_pos > dcd->z) return NULL; + if (v->vehstatus & VS_CRASHED) return v; + if (v->type != VEH_TRAIN) return v; + + if (!v->IsStoppedInDepot()) return dcd->compatible ? NULL : v; + + Train *t = Train::From(v); + if (t->IsArticulatedPart() || t->IsRearDualheaded()) return NULL; + + bool wagon; + if (t->IsFrontEngine()) { + wagon = false; + } else if (t->First()->IsFrontEngine()) { + return NULL; // only check conversion at the front part of a consist + } else { + wagon = true; + } + + bool ok_unchanged = true; // the consist can run itself on the new track + bool try_replace = false; // a vehicle in the consist has a replacement + + for (Vehicle *w = v; w != NULL; w = Train::From(w)->GetNextUnit()) { + bool compatible = dcd->compatible || HasBit(Train::From(w)->compatible_railtypes, dcd->target); + bool replacement = EngineHasReplacementForCompany(dcd->c, w->engine_type, w->group_id, dcd->target); + + if (!compatible && !replacement) return w; + + if (!compatible) ok_unchanged = false; + if (replacement) try_replace = true; + } + + assert(ok_unchanged || try_replace); + + if (try_replace) { + /* some vehicles in the consist have a replacement; try it */ + CommandCost cost; + SavedRandomSeeds saved_seeds; + SaveRandomSeeds(&saved_seeds); + bool nothing_to_do = true; + SetRailType(v->tile, dcd->target); // temporarily set to the new type to allow conversion of vehicles + if (wagon) { + cost = ReplaceFreeUnit(&v, dcd->flags & ~DC_EXEC, ¬hing_to_do, dcd->target); + } else { + cost = ReplaceChain(&v, dcd->flags & ~DC_EXEC, dcd->c->settings.renew_keep_length, ¬hing_to_do, dcd->target); + } + SetRailType(v->tile, dcd->source); // restore original type + /* the replacement test can leave a train stuck as unpowered */ + if (!wagon) t->RailtypeChanged(); + RestoreRandomSeeds(saved_seeds); + + if (cost.Succeeded()) { // succeeded to convert the consist + dcd->cost.AddCost(cost); + return NULL; + } + } + + /* consist conversion failed or a vehicle cannot be converted */ + return ok_unchanged ? NULL : v; +} + /** * Convert one rail type to the other. You can convert normal rail to * monorail/maglev easily or vice-versa. @@ -1415,7 +1505,7 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 TrainList affected_trains; CommandCost cost(EXPENSES_CONSTRUCTION); - CommandCost error = CommandCost(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); // by default, there is no track to convert. + CommandCost err = CommandCost(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); // by default, there is no track to convert. TileArea ta(tile, p1); TILE_AREA_LOOP(tile, ta) { TileType tt = GetTileType(tile); @@ -1430,7 +1520,7 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 case MP_ROAD: if (!IsLevelCrossing(tile)) continue; if (RailNoLevelCrossings(totype)) { - error.MakeError(STR_ERROR_CROSSING_DISALLOWED); + err.MakeError(STR_ERROR_CROSSING_DISALLOWED); continue; } break; @@ -1449,7 +1539,7 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 /* Trying to convert other's rail */ CommandCost ret = CheckTileOwnership(tile); if (ret.Failed()) { - error = ret; + err = ret; continue; } @@ -1458,10 +1548,38 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 /* Vehicle on the tile when not converting Rail <-> ElRail * Tunnels and bridges have special check later */ if (tt != MP_TUNNELBRIDGE) { - if (!IsCompatibleRail(type, totype)) { + if ((tt == MP_RAILWAY) && (GetRailTileType(tile) == RAIL_TILE_DEPOT)) { + struct DepotConversionData dcd = { Company::Get(_current_company), flags, CommandCost(EXPENSES_NEW_VEHICLES, 0), GetTileMaxZ(tile), type, totype, IsCompatibleRail(type, totype) }; + if (VehicleFromPos(tile, &dcd, &DepotConversionCheck, true) != NULL) { + err = CommandCost(STR_ERROR_TRAIN_IN_THE_WAY); + continue; + } + cost.AddCost(dcd.cost); + if ((flags & DC_EXEC) != 0) { + CommandCost cost(EXPENSES_NEW_VEHICLES, 0); + VehicleList engines, wagons; + BuildDepotVehicleList(VEH_TRAIN, tile, &engines, &wagons, true); + bool unused; + SetRailType(tile, totype); + for (uint i = 0; i < engines.Length(); i++) { + const Train *t = Train::From(engines[i]); + if (!t->IsStoppedInDepot() || !t->IsFrontEngine()) continue; + CommandCost c = ReplaceChain((Vehicle**)&t, flags, dcd.c->settings.renew_keep_length, &unused, totype); + if (c.Succeeded()) cost.AddCost(c); // if it fails, the original consist must be compatible with the new track type + } + for (uint i = 0; i < wagons.Length(); i++) { + const Train *t = Train::From(wagons[i]); + assert(t->IsStoppedInDepot()); // wagons should not wander around on their own + CommandCost c = ReplaceFreeUnit((Vehicle**)&t, flags, &unused, totype); + if (c.Succeeded()) cost.AddCost(c); // if it fails, the original consist must be compatible with the new track type + } + assert(cost.GetCost() == dcd.cost.GetCost()); + SetRailType(tile, type); + } + } else if (!IsCompatibleRail(type, totype)) { CommandCost ret = EnsureNoVehicleOnGround(tile); if (ret.Failed()) { - error = ret; + err = ret; continue; } } @@ -1524,7 +1642,7 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 if (!IsCompatibleRail(GetRailType(tile), totype)) { CommandCost ret = TunnelBridgeIsFree(tile, endtile); if (ret.Failed()) { - error = ret; + err = ret; continue; } } @@ -1584,7 +1702,7 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 } } - return (cost.GetCost() == 0) ? error : cost; + return (cost.GetCost() == 0) ? err : cost; } static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags) diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 52f5e7bb..a5baab97 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -53,6 +53,7 @@ #include "../smallmap_gui.h" #include "../news_func.h" #include "../group.h" +#include "../autoreplace_base.h" #include "table/strings.h" @@ -894,6 +895,16 @@ bool AfterLoadGame() } } + if (IsSavegameVersionBefore(164)) { + /* Initialize per-railtype rail renew lists */ + Company *c; + FOR_ALL_COMPANIES(c) { + for (RailType rt = RAILTYPE_BEGIN; rt < RAILTYPE_END; rt++) { + c->rail_renew_list[rt] = NULL; + } + } + } + if (IsSavegameVersionBefore(48)) { for (TileIndex t = 0; t < map_size; t++) { switch (GetTileType(t)) { diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp index 894611d6..c6082dd1 100644 --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -150,6 +150,7 @@ static const SaveLoad _company_settings_desc[] = { /* Engine renewal settings */ SLE_CONDNULL(512, 16, 18), SLE_CONDREF(Company, engine_renew_list, REF_ENGINE_RENEWS, 19, SL_MAX_VERSION), + SLE_CONDREFN(Company, rail_renew_list, REF_ENGINE_RENEWS, RAILTYPE_END, 164, SL_MAX_VERSION), SLE_CONDVAR(Company, settings.engine_renew, SLE_BOOL, 16, SL_MAX_VERSION), SLE_CONDVAR(Company, settings.engine_renew_months, SLE_INT16, 16, SL_MAX_VERSION), SLE_CONDVAR(Company, settings.engine_renew_money, SLE_UINT32, 16, SL_MAX_VERSION), @@ -172,6 +173,7 @@ static const SaveLoad _company_settings_skip_desc[] = { SLE_CONDNULL(512, 16, 18), SLE_CONDNULL(2, 19, 68), // engine_renew_list SLE_CONDNULL(4, 69, SL_MAX_VERSION), // engine_renew_list + SLE_CONDNULL(4 * RAILTYPE_END, 164, SL_MAX_VERSION), // rail_renew_list SLE_CONDNULL(1, 16, SL_MAX_VERSION), // settings.engine_renew SLE_CONDNULL(2, 16, SL_MAX_VERSION), // settings.engine_renew_months SLE_CONDNULL(4, 16, SL_MAX_VERSION), // settings.engine_renew_money diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 5b5c69ae..05b64b5f 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -228,8 +228,9 @@ * 161 22567 * 162 22713 * 163 22767 + * 164 ***** */ -extern const uint16 SAVEGAME_VERSION = 163; ///< Current savegame version of OpenTTD. +extern const uint16 SAVEGAME_VERSION = 164; ///< Current savegame version of OpenTTD. SavegameType _savegame_type; ///< type of savegame we are loading @@ -746,9 +747,9 @@ static inline byte SlCalcConvFileLen(VarType conv) } /** Return the size in bytes of a reference (pointer) */ -static inline size_t SlCalcRefLen() +static inline size_t SlCalcRefLen(size_t length) { - return IsSavegameVersionBefore(69) ? 2 : 4; + return (IsSavegameVersionBefore(69) ? 2 : 4) * length; } void SlSetArrayIndex(uint index) @@ -1388,7 +1389,7 @@ size_t SlCalcObjMemberLength(const void *object, const SaveLoad *sld) switch (sld->cmd) { case SL_VAR: return SlCalcConvFileLen(sld->conv); - case SL_REF: return SlCalcRefLen(); + case SL_REF: return SlCalcRefLen(sld->length); case SL_ARR: return SlCalcArrayLen(sld->length, sld->conv); case SL_STR: return SlCalcStringLen(GetVariableAddress(object, sld), sld->length, sld->conv); case SL_LST: return SlCalcListLen(GetVariableAddress(object, sld)); @@ -1422,17 +1423,25 @@ bool SlObjectMember(void *ptr, const SaveLoad *sld) case SL_REF: // Reference variable, translate switch (_sl.action) { case SLA_SAVE: - SlWriteUint32((uint32)ReferenceToInt(*(void **)ptr, (SLRefType)conv)); + for (uint i = 0; i < sld->length; i++) { + SlWriteUint32((uint32)ReferenceToInt(((void **)ptr)[i], (SLRefType)conv)); + } break; case SLA_LOAD_CHECK: case SLA_LOAD: - *(size_t *)ptr = IsSavegameVersionBefore(69) ? SlReadUint16() : SlReadUint32(); + for (uint i = 0; i < sld->length; i++) { + ((size_t *)ptr)[i] = IsSavegameVersionBefore(69) ? SlReadUint16() : SlReadUint32(); + } break; case SLA_PTRS: - *(void **)ptr = IntToReference(*(size_t *)ptr, (SLRefType)conv); + for (uint i = 0; i < sld->length; i++) { + ((void **)ptr)[i] = IntToReference(((size_t *)ptr)[i], (SLRefType)conv); + } break; case SLA_NULL: - *(void **)ptr = NULL; + for (uint i = 0; i < sld->length; i++) { + ((void **)ptr)[i] = NULL; + } break; default: NOT_REACHED(); } diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 0090b6be..b14b8061 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -243,7 +243,18 @@ typedef SaveLoad SaveLoadGlobVarList; * @param from First savegame version that has the field. * @param to Last savegame version that has the field. */ -#define SLE_CONDREF(base, variable, type, from, to) SLE_GENERAL(SL_REF, base, variable, type, 0, from, to) +#define SLE_CONDREF(base, variable, type, from, to) SLE_GENERAL(SL_REF, base, variable, type, 1, from, to) + +/** + * Storage of an array of references in some savegame versions. + * @param base Name of the class or struct containing the variable. + * @param variable Name of the variable in the class or struct referenced by \a base. + * @param type Type of the references, a value from #SLRefType. + * @param length Number of references (length of array). + * @param from First savegame version that has the field. + * @param to Last savegame version that has the field. + */ +#define SLE_CONDREFN(base, variable, type, length, from, to) SLE_GENERAL(SL_REF, base, variable, type, length, from, to) /** * Storage of an array in some savegame versions. diff --git a/src/vehicle.cpp b/src/vehicle.cpp index e5c239dc..00b80295 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -131,7 +131,18 @@ bool Vehicle::NeedsServicing() const if (needed_money > c->money) return false; for (const Vehicle *v = this; v != NULL; v = (v->type == VEH_TRAIN) ? Train::From(v)->GetNextUnit() : NULL) { - EngineID new_engine = EngineReplacementForCompany(c, v->engine_type, v->group_id); + EngineID new_engine; + + /* If it is a train, use the current railtype as conversion target first, + * assuming that's the most likely railtype for a nearby depot. */ + if (v->type == VEH_TRAIN) { + new_engine = EngineReplacementForCompany(c, v->engine_type, v->group_id, GetRailType(v->tile)); + if (new_engine == INVALID_ENGINE || !HasBit(Engine::Get(new_engine)->company_avail, v->owner)) { + new_engine = EngineReplacementForCompany(c, v->engine_type, v->group_id); + } + } else { + new_engine = EngineReplacementForCompany(c, v->engine_type, v->group_id); + } /* Check engine availability */ if (new_engine == INVALID_ENGINE || !HasBit(Engine::Get(new_engine)->company_avail, v->owner)) continue; @@ -359,7 +370,7 @@ bool HasVehicleOnPosXY(int x, int y, void *data, VehicleFromPosProc *proc) * all vehicles * @return the best matching or first vehicle (depending on find_first). */ -static Vehicle *VehicleFromPos(TileIndex tile, void *data, VehicleFromPosProc *proc, bool find_first) +Vehicle *VehicleFromPos(TileIndex tile, void *data, VehicleFromPosProc *proc, bool find_first) { int x = GB(TileX(tile), HASH_RES, HASH_BITS); int y = GB(TileY(tile), HASH_RES, HASH_BITS) << HASH_BITS;