/* * This file is part of OpenTTD. * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . */ /** @file tbtr_template_vehicle_func.cpp Template-based train replacement: template vehicle functions. */ #include "stdafx.h" #include "articulated_vehicles.h" #include "autoreplace_func.h" #include "cargoaction.h" #include "command_func.h" #include "company_func.h" #include "core/backup_type.hpp" #include "core/geometry_type.hpp" #include "core/random_func.hpp" #include "debug.h" #include "depot_base.h" #include "gfx_func.h" #include "newgrf.h" #include "spritecache.h" #include "strings_func.h" #include "table/sprites.h" #include "table/strings.h" #include "tbtr_template_vehicle_func.h" #include "tbtr_template_vehicle.h" #include "train.h" #include "vehicle_func.h" #include "vehicle_gui.h" #include "window_func.h" #include "window_gui.h" #include "zoom_func.h" #include #include #include "safeguards.h" bool _template_vehicle_images_valid = false; void BuildTemplateGuiList(GUITemplateList *list, Scrollbar *vscroll, Owner oid, RailType railtype) { list->clear(); for (const TemplateVehicle *tv : TemplateVehicle::Iterate()) { if (tv->owner == oid && (tv->IsPrimaryVehicle() || tv->IsFreeWagonChain()) && TemplateVehicleContainsEngineOfRailtype(tv, railtype)) { list->push_back(tv); } } list->RebuildDone(); if (vscroll) vscroll->SetCount((uint)list->size()); } Money CalculateOverallTemplateCost(const TemplateVehicle *tv) { Money val = 0; for (; tv; tv = tv->GetNextUnit()) { val += (Engine::Get(tv->engine_type))->GetCost(); } return val; } Money CalculateOverallTemplateDisplayRunningCost(const TemplateVehicle *tv) { Money val = 0; for (; tv; tv = tv->GetNextUnit()) { val += (Engine::Get(tv->engine_type))->GetDisplayRunningCost(); } return val; } void DrawTemplate(const TemplateVehicle *tv, int left, int right, int y, int height) { if (!tv) return; bool rtl = _current_text_dir == TD_RTL; DrawPixelInfo tmp_dpi; int max_width = right - left + 1; int veh_height = ScaleSpriteTrad(14); int padding = height - veh_height; if (!FillDrawPixelInfo(&tmp_dpi, left, y + (padding / 2), max_width, height)) return; AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi); const TemplateVehicle *t = tv; int offset = rtl ? max_width : 0; while (t) { t->sprite_seq.Draw(offset + ((rtl ? -1 : 1) * t->image_dimensions.GetOffsetX()), t->image_dimensions.GetOffsetY() + ScaleSpriteTrad(10), t->colourmap, false); offset += (rtl ? -1 : 1) * t->image_dimensions.GetDisplayImageWidth(); t = t->Next(); } } // copy important stuff from the virtual vehicle to the template void SetupTemplateVehicleFromVirtual(TemplateVehicle *tmp, TemplateVehicle *prev, Train *virt) { if (prev) { prev->SetNext(tmp); tmp->SetPrev(prev); tmp->SetFirst(prev->First()); } tmp->railtype = virt->railtype; tmp->owner = virt->owner; // set the subtype but also clear the virtual flag while doing it tmp->subtype = virt->subtype & ~(1 << GVSF_VIRTUAL); // set the cargo type and capacity tmp->cargo_type = virt->cargo_type; tmp->cargo_subtype = virt->cargo_subtype; tmp->cargo_cap = virt->cargo_cap; SB(tmp->ctrl_flags, TVCF_REVERSED, 1, HasBit(virt->flags, VRF_REVERSE_DIRECTION) ? 1 : 0); if (!virt->Previous()) { uint cargo_weight = 0; uint full_cargo_weight = 0; for (const Train *u = virt; u != nullptr; u = u->Next()) { cargo_weight += u->GetCargoWeight(u->cargo.StoredCount()); full_cargo_weight += u->GetCargoWeight(u->cargo_cap); } const GroundVehicleCache *gcache = virt->GetGroundVehicleCache(); tmp->max_speed = virt->GetDisplayMaxSpeed(); tmp->power = gcache->cached_power; tmp->empty_weight = std::max(gcache->cached_weight - cargo_weight, 1); tmp->full_weight = std::max(gcache->cached_weight + full_cargo_weight - cargo_weight, 1); tmp->max_te = gcache->cached_max_te; tmp->air_drag = gcache->cached_air_drag; } virt->GetImage(_current_text_dir == TD_RTL ? DIR_E : DIR_W, EIT_IN_DEPOT, &tmp->sprite_seq); tmp->image_dimensions.SetFromTrain(virt); tmp->colourmap = GetUncachedTrainPaletteIgnoringGroup(virt); } // create a full TemplateVehicle based train according to a virtual train TemplateVehicle* TemplateVehicleFromVirtualTrain(Train *virt) { assert(virt != nullptr); Train *init_virt = virt; TemplateVehicle *tmp = nullptr; TemplateVehicle *prev = nullptr; for (; virt; virt = virt->Next()) { tmp = new TemplateVehicle(virt->engine_type); SetupTemplateVehicleFromVirtual(tmp, prev, virt); prev = tmp; } tmp->First()->SetRealLength(CeilDiv(init_virt->gcache.cached_total_length * 10, TILE_SIZE)); return tmp->First(); } // forward declaration, defined in train_cmd.cpp CommandCost CmdSellRailWagon(DoCommandFlag flags, Vehicle *t, uint16_t data, uint32_t user); Train *DeleteVirtualTrain(Train *chain, Train *to_del) { if (chain != to_del) { CmdSellRailWagon(DC_EXEC, to_del, 0, 0); return chain; } else { chain = chain->GetNextUnit(); CmdSellRailWagon(DC_EXEC, to_del, 0, 0); return chain; } } // retrieve template vehicle from template replacement that belongs to the given group TemplateVehicle *GetTemplateVehicleByGroupID(GroupID gid) { if (gid >= NEW_GROUP) return nullptr; const TemplateID tid = GetTemplateIDByGroupID(gid); return tid != INVALID_TEMPLATE ? TemplateVehicle::GetIfValid(tid) : nullptr; } TemplateVehicle *GetTemplateVehicleByGroupIDRecursive(GroupID gid) { if (gid >= NEW_GROUP) return nullptr; const TemplateID tid = GetTemplateIDByGroupIDRecursive(gid); return tid != INVALID_TEMPLATE ? TemplateVehicle::GetIfValid(tid) : nullptr; } /** * Check a template consist whether it contains any engine of the given railtype */ bool TemplateVehicleContainsEngineOfRailtype(const TemplateVehicle *tv, RailType type) { if (type == INVALID_RAILTYPE) return true; /* For standard rail engines, allow only those */ if (type == RAILTYPE_BEGIN || type == RAILTYPE_RAIL) { while (tv) { if (tv->railtype != type) { return false; } tv = tv->GetNextUnit(); } return true; } /* For electrified rail engines, standard wagons or engines are allowed to be included */ while (tv) { if (tv->railtype == type) { return true; } tv = tv->GetNextUnit(); } return false; } Train *ChainContainsEngine(EngineID eid, Train *chain) { for (; chain != nullptr; chain = chain->GetNextUnit()) { if (chain->engine_type == eid) return chain; } return nullptr; } static bool IsTrainUsableAsTemplateReplacementSource(const Train *t) { if (t->First()->IsFreeWagon()) return true; if (t->IsPrimaryVehicle() && t->IsStoppedInDepot() && t->GetNextUnit() == nullptr) { if (t->GetNumOrders() != 0) return false; if (t->IsOrderListShared()) return false; if (t->group_id != DEFAULT_GROUP) return false; return true; } return false; } void TemplateDepotVehicles::Init(TileIndex tile) { FindVehicleOnPos(tile, VEH_TRAIN, this, [](Vehicle *v, void *data) -> Vehicle * { TemplateDepotVehicles *self = static_cast(data); self->vehicles.insert(v->index); return v; }); } void TemplateDepotVehicles::RemoveVehicle(VehicleID id) { this->vehicles.erase(id); } Train *TemplateDepotVehicles::ContainsEngine(EngineID eid, Train *not_in) { for (VehicleID id : this->vehicles) { Train *t = Train::GetIfValid(id); if (t == nullptr) continue; // conditions: v is stopped in the given depot, has the right engine and if 'not_in' is given v must not be contained within 'not_in' // if 'not_in' is nullptr, no check is needed if (t->owner == _current_company // If the veh belongs to a chain, wagons will not return true on IsStoppedInDepot(), only primary vehicles will // in case of t not a primary veh, we demand it to be a free wagon to consider it for replacement && IsTrainUsableAsTemplateReplacementSource(t) && t->engine_type == eid && (not_in == nullptr || not_in->First() != t->First())) { return t; } } return nullptr; } void NeutralizeStatus(Train *t) { DoCommand(t->tile, DEFAULT_GROUP, t->index, DC_EXEC, CMD_ADD_VEHICLE_GROUP); DoCommand(0, t->index | CO_UNSHARE << 30, 0, DC_EXEC, CMD_CLONE_ORDER); DoCommand(0, t->index, 0, DC_EXEC, CMD_RENAME_VEHICLE, nullptr); } TBTRDiffFlags TrainTemplateDifference(const Train *t, const TemplateVehicle *tv) { TBTRDiffFlags diff = TBTRDF_NONE; while (t != nullptr && tv != nullptr) { if (t->engine_type != tv->engine_type) { return TBTRDF_ALL; } if (tv->refit_as_template && (t->cargo_type != tv->cargo_type || t->cargo_subtype != tv->cargo_subtype)) { diff |= TBTRDF_REFIT; } if (HasBit(t->flags, VRF_REVERSE_DIRECTION) != HasBit(tv->ctrl_flags, TVCF_REVERSED)) { diff |= TBTRDF_DIR; } t = t->GetNextUnit(); tv = tv->GetNextUnit(); } if ((t != nullptr) != (tv != nullptr)) { return TBTRDF_ALL; } return diff; } void BreakUpRemainders(Train *t) { while (t) { Train *move; if (HasBit(t->subtype, GVSF_ENGINE)) { move = t; t = t->Next(); DoCommand(move->tile, move->index | (1 << 22), INVALID_VEHICLE, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); NeutralizeStatus(move); } else { t = t->Next(); } } } // make sure the real train wagon has the right cargo void CopyWagonStatus(TemplateVehicle *from, Train *to) { to->cargo_type = from->cargo_type; to->cargo_subtype = from->cargo_subtype; } uint CountsTrainsNeedingTemplateReplacement(GroupID g_id, const TemplateVehicle *tv) { uint count = 0; if (!tv) return count; for (Train *t : Train::IterateFrontOnly()) { if (t->IsPrimaryVehicle() && t->group_id == g_id && TrainTemplateDifference(t, tv) != TBTRDF_NONE) { count++; } } return count; } // refit each vehicle in t as is in tv, assume t and tv contain the same types of vehicles CommandCost CmdRefitTrainFromTemplate(Train *t, TemplateVehicle *tv, DoCommandFlag flags) { CommandCost cost(t->GetExpenseType(false)); while (t && tv) { // refit t as tv uint32_t cb = GetCmdRefitVeh(t); cost.AddCost(DoCommand(t->tile, t->index, tv->cargo_type | tv->cargo_subtype << 8 | (1 << 16) | (1 << 31), flags, cb)); // next t = t->GetNextUnit(); tv = tv->GetNextUnit(); } return cost; } // set unit direction of each vehicle in t as is in tv, assume t and tv contain the same types of vehicles CommandCost CmdSetTrainUnitDirectionFromTemplate(Train *t, TemplateVehicle *tv, DoCommandFlag flags) { CommandCost cost(t->GetExpenseType(false)); while (t && tv) { // refit t as tv if (HasBit(t->flags, VRF_REVERSE_DIRECTION) != HasBit(tv->ctrl_flags, TVCF_REVERSED)) { cost.AddCost(DoCommand(t->tile, t->index, true, flags, CMD_REVERSE_TRAIN_DIRECTION | CMD_MSG(STR_ERROR_CAN_T_REVERSE_DIRECTION_RAIL_VEHICLE))); } // next t = t->GetNextUnit(); tv = tv->GetNextUnit(); } return cost; } /** using cmdtemplatereplacevehicle as test-function (i.e. with flag DC_NONE) is not a good idea as that function relies on * actually moving vehicles around to work properly. * We do this worst-cast test instead. */ CommandCost TestBuyAllTemplateVehiclesInChain(TemplateVehicle *tv, TileIndex tile) { CommandCost cost(EXPENSES_NEW_VEHICLES); for (; tv; tv = tv->GetNextUnit()) { cost.AddCost(DoCommand(tile, tv->engine_type, 0, DC_NONE, CMD_BUILD_VEHICLE)); } return cost; } /** Transfer as much cargo from a given (single train) vehicle onto a chain of vehicles. * I.e., iterate over the chain from head to tail and use all available cargo capacity (w.r.t. cargo type of course) * to store the cargo from the given single vehicle. * @param old_veh: ptr to the single vehicle, which's cargo shall be moved * @param new_head: ptr to the head of the chain, which shall obtain old_veh's cargo * @return: amount of moved cargo, TODO */ void TransferCargoForTrain(Train *old_veh, Train *new_head) { assert(new_head->IsPrimaryVehicle() || new_head->IsFreeWagon()); const CargoID cargo_type = old_veh->cargo_type; const uint8_t cargo_subtype = old_veh->cargo_subtype; // how much cargo has to be moved (if possible) uint remainingAmount = old_veh->cargo.TotalCount(); // each vehicle in the new chain shall be given as much of the old cargo as possible, until none is left for (Train *tmp = new_head; tmp != nullptr && remainingAmount > 0; tmp = tmp->GetNextUnit()) { if (tmp->cargo_type == cargo_type && tmp->cargo_subtype == cargo_subtype) { // calculate the free space for new cargo on the current vehicle uint curCap = tmp->cargo_cap - tmp->cargo.TotalCount(); uint moveAmount = std::min(remainingAmount, curCap); // move (parts of) the old vehicle's cargo onto the current vehicle of the new chain if (moveAmount > 0) { old_veh->cargo.Shift(moveAmount, &tmp->cargo); remainingAmount -= moveAmount; } } } /* Update train weight etc., the old vehicle will be sold anyway */ new_head->ConsistChanged(CCF_LOADUNLOAD); } void UpdateAllTemplateVehicleImages() { SavedRandomSeeds saved_seeds; SaveRandomSeeds(&saved_seeds); for (TemplateVehicle *tv : TemplateVehicle::Iterate()) { if (tv->Prev() == nullptr) { Backup cur_company(_current_company, tv->owner, FILE_LINE); StringID err; Train* t = VirtualTrainFromTemplateVehicle(tv, err, 0); if (t != nullptr) { int tv_len = 0; for (TemplateVehicle *u = tv; u != nullptr; u = u->Next()) { tv_len++; } int t_len = 0; for (Train *u = t; u != nullptr; u = u->Next()) { t_len++; } if (t_len == tv_len) { Train *v = t; for (TemplateVehicle *u = tv; u != nullptr; u = u->Next(), v = v->Next()) { v->GetImage(_current_text_dir == TD_RTL ? DIR_E : DIR_W, EIT_IN_DEPOT, &u->sprite_seq); u->image_dimensions.SetFromTrain(v); u->colourmap = GetVehiclePalette(v); } } else { DEBUG(misc, 0, "UpdateAllTemplateVehicleImages: vehicle count mismatch: %u, %u", t_len, tv_len); } delete t; } cur_company.Restore(); } } RestoreRandomSeeds(saved_seeds); _template_vehicle_images_valid = true; } int GetTemplateVehicleEstimatedMaxAchievableSpeed(const TemplateVehicle *tv, int mass, const int speed_cap) { int max_speed = 0; int acceleration; if (mass < 1) mass = 1; do { max_speed++; acceleration = GetTrainRealisticAccelerationAtSpeed(max_speed, mass, tv->power, tv->max_te, tv->air_drag, tv->railtype); } while (acceleration > 0 && max_speed < speed_cap); return max_speed; }