diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj index 0571ef4862..3de54c7f64 100644 --- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -291,6 +291,16 @@ + + + + + + + + + + @@ -871,6 +881,8 @@ + + diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters index 0b6dc573b4..992eef0d72 100644 --- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -102,6 +102,36 @@ + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + Source Files @@ -1842,6 +1872,12 @@ Save/Load handlers + + Save/Load handlers + + + Save/Load handlers + Tables diff --git a/projects/openttd_vs140.vcxproj b/projects/openttd_vs140.vcxproj index b82aba741c..8b126b63a3 100644 --- a/projects/openttd_vs140.vcxproj +++ b/projects/openttd_vs140.vcxproj @@ -308,6 +308,16 @@ + + + + + + + + + + @@ -888,6 +898,8 @@ + + diff --git a/projects/openttd_vs140.vcxproj.filters b/projects/openttd_vs140.vcxproj.filters index 0b6dc573b4..992eef0d72 100644 --- a/projects/openttd_vs140.vcxproj.filters +++ b/projects/openttd_vs140.vcxproj.filters @@ -102,6 +102,36 @@ + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + Source Files @@ -1842,6 +1872,12 @@ Save/Load handlers + + Save/Load handlers + + + Save/Load handlers + Tables diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj index f6b938ed7d..77b9091858 100644 --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -434,6 +434,46 @@ + + + + + + + + + + + + + + + + + + + + @@ -2778,6 +2818,14 @@ RelativePath=".\..\src\saveload\extended_ver_sl.cpp" > + + + + + + + + + + + + + + + + + + + + + + + + @@ -2775,6 +2815,14 @@ RelativePath=".\..\src\saveload\extended_ver_sl.cpp" > + + + + { */ inline void ClearFreeWagon() { ClrBit(this->subtype, GVSF_FREE_WAGON); } + /** + * Set a vehicle as a virtual vehicle. + */ + inline void SetVirtual() { SetBit(this->subtype, GVSF_VIRTUAL); } + + /** + * Clear a vehicle from being a virtual vehicle. + */ + inline void ClearVirtual() { ClrBit(this->subtype, GVSF_VIRTUAL); } + /** * Set a vehicle as a multiheaded engine. */ @@ -329,6 +339,12 @@ struct GroundVehicle : public SpecializedVehicle { */ inline bool IsMultiheaded() const { return HasBit(this->subtype, GVSF_MULTIHEADED); } + /** + * Tell if we are dealing with a virtual vehicle (used for templates). + * @return True if the vehicle is a virtual vehicle. + */ + inline bool IsVirtual() const { return HasBit(this->subtype, GVSF_VIRTUAL); } + /** * Tell if we are dealing with the rear end of a multiheaded engine. * @return True if the engine is the rear part of a dualheaded engine. diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp index 12cce41f74..55873fb8d4 100644 --- a/src/group_cmd.cpp +++ b/src/group_cmd.cpp @@ -21,6 +21,7 @@ #include "company_func.h" #include "core/pool_func.hpp" #include "order_backup.h" +#include "tbtr_template_vehicle.h" #include "table/strings.h" @@ -137,6 +138,9 @@ void GroupStatistics::Clear() */ /* static */ void GroupStatistics::CountVehicle(const Vehicle *v, int delta) { + /* make virtual trains group-neutral */ + if ( HasBit(v->subtype, GVSF_VIRTUAL) ) return; + assert(delta == 1 || delta == -1); GroupStatistics &stats_all = GroupStatistics::GetAllGroup(v); @@ -341,6 +345,9 @@ CommandCost CmdDeleteGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 VehicleType vt = g->vehicle_type; + /* Delete all template replacements using the just deleted group */ + deleteIllegalTemplateReplacements(g->index); + /* Delete the Replace Vehicle Windows */ DeleteWindowById(WC_REPLACE_VEHICLE, g->vehicle_type); delete g; diff --git a/src/group_gui.cpp b/src/group_gui.cpp index 81fb1202e4..0bee1b13ec 100644 --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -25,6 +25,7 @@ #include "vehicle_gui_base.h" #include "core/geometry_func.hpp" #include "company_base.h" +#include "tbtr_template_gui_main.h" #include "widgets/group_widget.h" @@ -397,7 +398,7 @@ public: break; case WID_GL_MANAGE_VEHICLES_DROPDOWN: { - Dimension d = this->GetActionDropdownSize(true, true); + Dimension d = this->GetActionDropdownSize(true, true, this->vli.vtype == VEH_TRAIN); d.height += padding.height; d.width += padding.width; *size = maxdim(*size, d); @@ -643,6 +644,7 @@ public: case WID_GL_DELETE_GROUP: { // Delete the selected group this->group_confirm = this->vli.index; ShowQuery(STR_QUERY_GROUP_DELETE_CAPTION, STR_GROUP_DELETE_QUERY_TEXT, this, DeleteGroupCallback); + InvalidateWindowData(WC_TEMPLATEGUI_MAIN, 0, 0, 0); break; } @@ -655,7 +657,7 @@ public: break; case WID_GL_MANAGE_VEHICLES_DROPDOWN: { - DropDownList *list = this->BuildActionDropdownList(true, Group::IsValidID(this->vli.index)); + DropDownList *list = this->BuildActionDropdownList(true, Group::IsValidID(this->vli.index), this->vli.vtype == VEH_TRAIN); ShowDropDownList(this, list, 0, WID_GL_MANAGE_VEHICLES_DROPDOWN); break; } @@ -762,6 +764,7 @@ public: virtual void OnQueryTextFinished(char *str) { if (str != NULL) DoCommandP(0, this->group_rename, 0, CMD_ALTER_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_RENAME), NULL, str); + InvalidateWindowData(WC_TEMPLATEGUI_MAIN, 0, 0, 0); this->group_rename = INVALID_GROUP; } @@ -782,6 +785,11 @@ public: assert(this->vehicles.Length() != 0); switch (index) { + case ADI_TEMPLATE_REPLACE: // TemplateReplace Window + if (vli.vtype == VEH_TRAIN) { + ShowTemplateReplaceWindow(this->unitnumber_digits, this->resize.step_height); + } + break; case ADI_REPLACE: // Replace window ShowReplaceGroupVehicleWindow(this->vli.index, this->vli.vtype); break; diff --git a/src/lang/english.txt b/src/lang/english.txt index 0bd430bad0..cad97b1066 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -4968,3 +4968,62 @@ STR_PLANE :{BLACK}{PLANE} STR_SHIP :{BLACK}{SHIP} STR_TOOLBAR_RAILTYPE_VELOCITY :{STRING} ({VELOCITY}) + +STR_TMPL_RPL_TITLE :{WHITE}Template Replacement +STR_TMPL_TEMPLATE_REPLACEMENT :Template Replacement +STR_TMPL_TRAINS_IN_GROUP :{BLACK}Trains in group +STR_TMPL_AVAILABLE_TEMPLATES :{BLACK}Available Templates +STR_TMPL_DEFINE_TEMPLATE :{BLACK}New +STR_TMPL_EDIT_TEMPLATE :{BLACK}Edit +STR_TMPL_CREATE_CLONE_VEH :{BLACK}Clone +STR_TMPL_DELETE_TEMPLATE :{BLACK}Delete +STR_TMPL_RPL_ALL_TMPL :{BLACK}Replace All Templates +STR_TMPL_NEW_VEHICLE :{BLACK}New Vehicle +STR_TMPL_CONFIRM :{BLACK}Ok +STR_TMPL_CANCEL :{BLACK}Cancel +STR_TMPL_NEW :{BLACK}New Template Vehicle +STR_TMPL_REFIT :{BLACK}Refit +STR_TMPL_GROUP_INFO :{BLACK}Group Info: {ORANGE} +STR_TMPL_TEMPLATE_INFO :{BLACK}Template Info: {ORANGE} +STR_TMPL_RPL_START :{BLACK}Start replacing +STR_TMPL_RPL_STOP :{BLACK}Stop replacing +STR_TMPL_TRAIN_OVR_VALUE :{TINY_FONT}{BLACK}Train Value: {CURRENCY_SHORT} +STR_TMPL_TEMPLATE_OVR_VALUE :{TINY_FONT}{BLACK}Buying Cost: {GOLD}{CURRENCY_LONG} +STR_TMPL_TEMPLATE_OVR_VALUE_nogold :{TINY_FONT}{BLACK}Buying Cost: {CURRENCY_LONG} +STR_TMPL_TEMPLATE_OVR_VALUE_nogoldandcurrency :{TINY_FONT}{BLACK}Buying Cost: +STR_TMPL_TEMPLATE_OVR_VALUE_notinyfont :{BLACK}Buying Cost: {GOLD}{CURRENCY_LONG} +STR_TMPL_TEMPLATE_OVR_VALUE_notinyfontandblack :Buying Cost: {GOLD}{CURRENCY_LONG} +STR_TMPL_WARNING_FREE_WAGON :{RED}Free Chain: not runnable! +STR_TMPL_TEST :{ORANGE}Test String: {RAW_STRING} {RAW_STRING} +STR_TMPL_GROUP_USES_TEMPLATE :{BLACK}Template in use: {NUM} +STR_TMP_TEMPLATE_IN_USE :Template is in use +STR_TMPL_GROUP_NUM_TRAINS :{BLACK}{NUM} +STR_TMPL_CREATEGUI_TITLE :{WHITE}Create/Edit Template Vehicle +STR_TMPL_MAINGUI_DEFINEDGROUPS :{BLACK}Defined Groups for Company +STR_TMPL_TMPLRPL_EX_DIFF_RAILTYPE :Uses Template of different rail type + +STR_TMPL_SET_USEDEPOT :{BLACK}Use vehicles in depot +STR_TMPL_SET_USEDEPOT_TIP :{BLACK}Use vehicles inside the depot that are in a neutral and idle state to compose trains on template replacement in order to reduce buying costs +STR_TMPL_SET_KEEPREMAINDERS :{BLACK}Keep remainders +STR_TMPL_SET_KEEPREMAINDERS_TIP :{BLACK}After finishing template replacement keep all remaining vehicles from the old train in a neutral and idle state for later use +STR_TMPL_SET_REFIT :{BLACK}Use Refit +STR_TMPL_SET_REFIT_TIP :{BLACK}If set, the train will use exactly the cargo refit specified by the template. If not every wagon that is to be newly bought or retrieved from the depot, will *attempt* to be refitted as the old one was. Standard refit if this is impossible. + +STR_TMPL_CONFIG_USEDEPOT :use depot +STR_TMPL_CONFIG_KEEPREMAINDERS :keep rem +STR_TMPL_CONFIG_REFIT :refit + +STR_TMPL_NUM_TRAINS_NEED_RPL :# trains to replace: + +STR_TMPL_CARGO_SUMMARY :{CARGO_LONG} +STR_TMPL_CARGO_SUMMARY_MULTI :{CARGO_LONG} (x{NUM}) + +STR_TMPL_RPLALLGUI_TITLE :{WHITE}Replace all Templace Vehicles +STR_TMPL_RPLALLGUI_INSET_TOP :{BLACK}Choose Vehicle Type and Replacement +STR_TMPL_RPLALLGUI_INSET_TOP_1 :{BLACK}Template Engines +STR_TMPL_RPLALLGUI_INSET_TOP_2 :{BLACK}Buyable Engines +STR_TMPL_RPLALLGUI_INSET_BOTTOM :{BLACK}Current Template List (updated only after replacement) +STR_TMPL_RPLALLGUI_BUTTON_RPLALL :{BLACK}Replace All +STR_TMPL_RPLALLGUI_BUTTON_APPLY :{BLACK}Apply +STR_TMPL_RPLALLGUI_BUTTON_CANCEL :{BLACK}Cancel +STR_TMPL_RPLALLGUI_USE_TIP :{BLACK}Select a vehicle type from each list and press 'Replace All'. If you are happy with the result displayed in the template list, press 'Apply' to actually apply these changes. diff --git a/src/network/network_command.cpp b/src/network/network_command.cpp index 6e5458fd86..6862191473 100644 --- a/src/network/network_command.cpp +++ b/src/network/network_command.cpp @@ -51,6 +51,10 @@ static CommandCallback * const _callback_table[] = { /* 0x19 */ CcStartStopVehicle, /* 0x1A */ CcGame, /* 0x1B */ CcAddVehicleNewGroup, + /* 0x1C */ CcSetVirtualTrain, + /* 0x1D */ CcVirtualTrainWaggonsMoved, + /* 0x1E */ CcDeleteVirtualTrain, + /* 0x1F */ CcAddVirtualEngine, }; /** diff --git a/src/newgrf.h b/src/newgrf.h index 752873a60e..8a894b872c 100644 --- a/src/newgrf.h +++ b/src/newgrf.h @@ -196,4 +196,6 @@ bool GetGlobalVariable(byte param, uint32 *value, const GRFFile *grffile); StringID MapGRFStringID(uint32 grfid, StringID str); void ShowNewGRFError(); +struct TemplateVehicle; + #endif /* NEWGRF_H */ diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 57b29f3f53..1124bea45f 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -1771,7 +1771,8 @@ void CheckOrders(const Vehicle *v) if (v->FirstShared() != v) return; /* Only check every 20 days, so that we don't flood the message log */ - if (v->owner == _local_company && v->day_counter % 20 == 0) { + /* The check is skipped entirely in case the current vehicle is virtual (a.k.a a 'template train') */ + if (v->owner == _local_company && v->day_counter % 20 == 0 && !HasBit(v->subtype, GVSF_VIRTUAL)) { const Order *order; StringID message = INVALID_STRING_ID; diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 9690481154..9b3b7c27e9 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -775,6 +775,9 @@ bool AfterLoadGame() /* Update all vehicles */ AfterLoadVehicles(true); + /* Update template vehicles */ + AfterLoadTemplateVehicles(); + /* Make sure there is an AI attached to an AI company */ { Company *c; diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index d83fc74c72..aa5609735c 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -44,6 +44,8 @@ #include "../fios.h" #include "../error.h" +#include "../tbtr_template_vehicle.h" + #include "table/strings.h" #include "saveload_internal.h" @@ -454,6 +456,8 @@ extern const ChunkHandler _linkgraph_chunk_handlers[]; extern const ChunkHandler _airport_chunk_handlers[]; extern const ChunkHandler _object_chunk_handlers[]; extern const ChunkHandler _persistent_storage_chunk_handlers[]; +extern const ChunkHandler _template_replacement_chunk_handlers[]; +extern const ChunkHandler _template_vehicle_chunk_handlers[]; /** Array of all chunks in a savegame, \c NULL terminated. */ static const ChunkHandler * const _chunk_handlers[] = { @@ -491,6 +495,8 @@ static const ChunkHandler * const _chunk_handlers[] = { _airport_chunk_handlers, _object_chunk_handlers, _persistent_storage_chunk_handlers, + _template_replacement_chunk_handlers, + _template_vehicle_chunk_handlers, NULL, }; @@ -1265,6 +1271,7 @@ static size_t ReferenceToInt(const void *obj, SLRefType rt) switch (rt) { case REF_VEHICLE_OLD: // Old vehicles we save as new ones case REF_VEHICLE: return ((const Vehicle*)obj)->index + 1; + case REF_TEMPLATE_VEHICLE: return ((const TemplateVehicle*)obj)->index + 1; case REF_STATION: return ((const Station*)obj)->index + 1; case REF_TOWN: return ((const Town*)obj)->index + 1; case REF_ORDER: return ((const Order*)obj)->index + 1; @@ -1324,6 +1331,10 @@ static void *IntToReference(size_t index, SLRefType rt) if (Vehicle::IsValidID(index)) return Vehicle::Get(index); SlErrorCorrupt("Referencing invalid Vehicle"); + case REF_TEMPLATE_VEHICLE: + if (TemplateVehicle::IsValidID(index)) return TemplateVehicle::Get(index); + SlErrorCorrupt("Referencing invalid TemplateVehicle"); + case REF_STATION: if (Station::IsValidID(index)) return Station::Get(index); SlErrorCorrupt("Referencing invalid Station"); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 8df011958b..5bac216bf2 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -88,6 +88,7 @@ enum SLRefType { REF_STORAGE = 9, ///< Load/save a reference to a persistent storage. REF_LINK_GRAPH = 10, ///< Load/save a reference to a link graph. REF_LINK_GRAPH_JOB = 11, ///< Load/save a reference to a link graph job. + REF_TEMPLATE_VEHICLE = 12, ///< Load/save a reference to a template vehicle }; /** Highest possible savegame version. */ diff --git a/src/saveload/saveload_internal.h b/src/saveload/saveload_internal.h index 74e5b9936d..67b3038a04 100644 --- a/src/saveload/saveload_internal.h +++ b/src/saveload/saveload_internal.h @@ -28,6 +28,7 @@ const SaveLoad *GetBaseStationDescription(); void AfterLoadVehicles(bool part_of_load); void FixupTrainLengths(); +void AfterLoadTemplateVehicles(); void AfterLoadStations(); void AfterLoadRoadStops(); void AfterLoadLabelMaps(); diff --git a/src/saveload/tbtr_template_replacement_sl.cpp b/src/saveload/tbtr_template_replacement_sl.cpp new file mode 100644 index 0000000000..d9b1448ebe --- /dev/null +++ b/src/saveload/tbtr_template_replacement_sl.cpp @@ -0,0 +1,35 @@ +#include "../stdafx.h" + +#include "../tbtr_template_vehicle.h" + +#include "saveload.h" + +static const SaveLoad _template_replacement_desc[] = { + SLE_VAR(TemplateReplacement, sel_template, SLE_UINT16), + SLE_VAR(TemplateReplacement, group, SLE_UINT16), + SLE_END() +}; + +static void Save_TMPL_RPLS() +{ + TemplateReplacement *tr; + + FOR_ALL_TEMPLATE_REPLACEMENTS(tr) { + SlSetArrayIndex(tr->index); + SlObject(tr, _template_replacement_desc); + } +} + +static void Load_TMPL_RPLS() +{ + int index; + + while ((index = SlIterateArray()) != -1) { + TemplateReplacement *tr = new (index) TemplateReplacement(); + SlObject(tr, _template_replacement_desc); + } +} + +extern const ChunkHandler _template_replacement_chunk_handlers[] = { + {'TRPL', Save_TMPL_RPLS, Load_TMPL_RPLS, NULL, NULL, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/tbtr_template_veh_sl.cpp b/src/saveload/tbtr_template_veh_sl.cpp new file mode 100644 index 0000000000..3c6ca9e0b6 --- /dev/null +++ b/src/saveload/tbtr_template_veh_sl.cpp @@ -0,0 +1,99 @@ +#include "../stdafx.h" + +#include "../tbtr_template_vehicle.h" + +#include "saveload.h" + +const SaveLoad* GTD() { + + static const SaveLoad _template_veh_desc[] = { + SLE_REF(TemplateVehicle, next, REF_TEMPLATE_VEHICLE), + + SLE_VAR(TemplateVehicle, reuse_depot_vehicles, SLE_UINT8), + SLE_VAR(TemplateVehicle, keep_remaining_vehicles, SLE_UINT8), + SLE_VAR(TemplateVehicle, refit_as_template, SLE_UINT8), + + SLE_VAR(TemplateVehicle, owner, SLE_UINT32), + SLE_VAR(TemplateVehicle, owner_b, SLE_UINT8), + + SLE_VAR(TemplateVehicle, engine_type, SLE_UINT16), + SLE_VAR(TemplateVehicle, cargo_type, SLE_UINT8), + SLE_VAR(TemplateVehicle, cargo_cap, SLE_UINT16), + SLE_VAR(TemplateVehicle, cargo_subtype, SLE_UINT8), + + SLE_VAR(TemplateVehicle, subtype, SLE_UINT8), + SLE_VAR(TemplateVehicle, railtype, SLE_UINT8), + + SLE_VAR(TemplateVehicle, index, SLE_UINT32), + + SLE_VAR(TemplateVehicle, real_consist_length, SLE_UINT16), + + SLE_VAR(TemplateVehicle, max_speed, SLE_UINT16), + SLE_VAR(TemplateVehicle, power, SLE_UINT32), + SLE_VAR(TemplateVehicle, weight, SLE_UINT32), + SLE_VAR(TemplateVehicle, max_te, SLE_UINT32), + + SLE_VAR(TemplateVehicle, spritenum, SLE_UINT8), + SLE_VAR(TemplateVehicle, cur_image, SLE_UINT32), + SLE_VAR(TemplateVehicle, image_width, SLE_UINT32), + + SLE_END() + }; + + static const SaveLoad * const _ret[] = { + _template_veh_desc, + }; + + return _ret[0]; +} + +static void Save_TMPLS() +{ + TemplateVehicle *tv; + + FOR_ALL_TEMPLATES(tv) { + SlSetArrayIndex(tv->index); + SlObject(tv, GTD()); + } +} + +static void Load_TMPLS() +{ + int index; + + while ((index = SlIterateArray()) != -1) { + TemplateVehicle *tv = new (index) TemplateVehicle(); //TODO:check with veh sl code + SlObject(tv, GTD()); + } +} + +static void Ptrs_TMPLS() +{ + TemplateVehicle *tv; + FOR_ALL_TEMPLATES(tv) { + SlObject(tv, GTD()); + } +} + +void AfterLoadTemplateVehicles() +{ + TemplateVehicle *tv; + + FOR_ALL_TEMPLATES(tv) { + /* Reinstate the previous pointer */ + if (tv->next != NULL) tv->next->previous = tv; + tv->first =NULL; + } + FOR_ALL_TEMPLATES(tv) { + /* Fill the first pointers */ + if (tv->previous == NULL) { + for (TemplateVehicle *u = tv; u != NULL; u = u->Next()) { + u->first = tv; + } + } + } +} + +extern const ChunkHandler _template_vehicle_chunk_handlers[] = { + {'TMPL', Save_TMPLS, Load_TMPLS, Ptrs_TMPLS, NULL, CH_ARRAY | CH_LAST}, +}; diff --git a/src/tbtr_template_gui_create.cpp b/src/tbtr_template_gui_create.cpp new file mode 100644 index 0000000000..9e4cce687b --- /dev/null +++ b/src/tbtr_template_gui_create.cpp @@ -0,0 +1,577 @@ +/* $Id$ */ + +/* + * 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_gui_create.cpp Template-based train replacement: template creation GUI. */ + +#include "stdafx.h" + +#include "gfx_func.h" +#include "direction_type.h" + +#include "strings_func.h" +#include "window_func.h" +#include "company_func.h" +#include "window_gui.h" +#include "settings_func.h" +#include "core/geometry_func.hpp" +#include "table/sprites.h" +#include "table/strings.h" +#include "viewport_func.h" +#include "window_func.h" +#include "gui.h" +#include "textbuf_gui.h" +#include "command_func.h" +#include "depot_base.h" +#include "vehicle_gui.h" +#include "spritecache.h" +#include "strings_func.h" +#include "window_func.h" +#include "vehicle_func.h" +#include "company_func.h" +#include "tilehighlight_func.h" +#include "window_gui.h" +#include "vehiclelist.h" +#include "order_backup.h" +#include "group.h" +#include "company_base.h" +#include "train.h" + +#include "tbtr_template_gui_create.h" +#include "tbtr_template_vehicle.h" +#include "tbtr_template_vehicle_func.h" + +#include "safeguards.h" + +class TemplateReplaceWindow; + +// some space in front of the virtual train in the matrix +uint16 TRAIN_FRONT_SPACE = 16; + +enum TemplateReplaceWindowWidgets { + TCW_CAPTION, + TCW_NEW_TMPL_PANEL, + TCW_INFO_PANEL, + TCW_SCROLLBAR_H_NEW_TMPL, + TCW_SCROLLBAR_V_NEW_TMPL, + TCW_SELL_TMPL, + TCW_NEW, + TCW_OK, + TCW_CANCEL, + TCW_REFIT, + TCW_CLONE, +}; + +static const NWidgetPart _widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, TCW_CAPTION), SetDataTip(STR_TMPL_CREATEGUI_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_SHADEBOX, COLOUR_GREY), + NWidget(WWT_DEFSIZEBOX, COLOUR_GREY), + NWidget(WWT_STICKYBOX, COLOUR_GREY), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_VERTICAL), + NWidget(WWT_PANEL, COLOUR_GREY, TCW_NEW_TMPL_PANEL), SetMinimalSize(250, 30), SetResize(1, 0), SetScrollbar(TCW_SCROLLBAR_H_NEW_TMPL), EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY, TCW_INFO_PANEL), SetMinimalSize(250, 100), SetResize(1, 1), SetScrollbar(TCW_SCROLLBAR_V_NEW_TMPL), EndContainer(), + NWidget(NWID_HSCROLLBAR, COLOUR_GREY, TCW_SCROLLBAR_H_NEW_TMPL), + EndContainer(), + NWidget(WWT_IMGBTN, COLOUR_GREY, TCW_SELL_TMPL), SetMinimalSize(40, 40), SetDataTip(0x0, STR_NULL), SetResize(0, 1), SetFill(0, 1), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, TCW_SCROLLBAR_V_NEW_TMPL), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TCW_OK), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_CONFIRM, STR_TMPL_CONFIRM), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TCW_NEW), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_NEW, STR_TMPL_NEW), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TCW_CLONE), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_CREATE_CLONE_VEH, STR_TMPL_CREATE_CLONE_VEH), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TCW_REFIT), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_REFIT, STR_TMPL_REFIT), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TCW_CANCEL), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_CANCEL, STR_TMPL_CANCEL), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), +}; + +static WindowDesc _template_create_window_desc( + WDP_AUTO, // window position + "template create window", // const char* ini_key + 456, 100, // window size + WC_CREATE_TEMPLATE, // window class + WC_TEMPLATEGUI_MAIN, // parent window class + WDF_CONSTRUCTION, // window flags + _widgets, lengthof(_widgets) // widgets + num widgets +); + +static void TrainDepotMoveVehicle(const Vehicle *wagon, VehicleID sel, const Vehicle *head) +{ + const Vehicle *v = Vehicle::Get(sel); + + if (v == wagon) return; + + if (wagon == NULL) { + if (head != NULL) wagon = head->Last(); + } else { + wagon = wagon->Previous(); + if (wagon == NULL) return; + } + + if (wagon == v) return; + + DoCommandP(v->tile, v->index | ((_ctrl_pressed ? 1 : 0) << 20) | (1 << 21) , wagon == NULL ? INVALID_VEHICLE : wagon->index, + CMD_MOVE_RAIL_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_MOVE_VEHICLE), CcVirtualTrainWaggonsMoved); +} + +class TemplateCreateWindow : public Window { +private: + Scrollbar *hscroll; + Scrollbar *vscroll; + int line_height; + Train* virtual_train; + bool editMode; + bool *noticeParent; + bool *createWindowOpen; /// used to notify main window of progress (dummy way of disabling 'delete' while editing a template) + bool virtualTrainChangedNotice; + VehicleID sel; + VehicleID vehicle_over; + TemplateVehicle *editTemplate; + +public: + TemplateCreateWindow(WindowDesc* _wdesc, TemplateVehicle *to_edit, bool *notice, bool *windowOpen, int step_h) : Window(_wdesc) + { + this->line_height = step_h; + this->CreateNestedTree(_wdesc != NULL); + this->hscroll = this->GetScrollbar(TCW_SCROLLBAR_H_NEW_TMPL); + this->vscroll = this->GetScrollbar(TCW_SCROLLBAR_V_NEW_TMPL); + this->FinishInitNested(VEH_TRAIN); + /* a sprite */ + this->GetWidget(TCW_SELL_TMPL)->widget_data = SPR_SELL_TRAIN; + + this->owner = _local_company; + + noticeParent = notice; + createWindowOpen = windowOpen; + virtualTrainChangedNotice = false; + this->editTemplate = to_edit; + + editMode = (to_edit != NULL); + + this->sel = INVALID_VEHICLE; + this->vehicle_over = INVALID_VEHICLE; + + if (to_edit) { + DoCommandP(0, to_edit->index, 0, CMD_VIRTUAL_TRAIN_FROM_TEMPLATE_VEHICLE, CcSetVirtualTrain); + } + + this->resize.step_height = 1; + + UpdateButtonState(); + } + + ~TemplateCreateWindow() + { + if (virtual_train != NULL) { + DoCommandP(0, virtual_train->index, 0, CMD_DELETE_VIRTUAL_TRAIN); + virtual_train = NULL; + } + + SetWindowClassesDirty(WC_TRAINS_LIST); + + /* more cleanup */ + *createWindowOpen = false; + DeleteWindowById(WC_BUILD_VIRTUAL_TRAIN, this->window_number); + } + + void SetVirtualTrain(Train* const train) + { + if (virtual_train != NULL) { + DoCommandP(0, virtual_train->index, 0, CMD_DELETE_VIRTUAL_TRAIN); + } + + virtual_train = train; + UpdateButtonState(); + } + + virtual void OnResize() + { + NWidgetCore *template_panel = this->GetWidget(TCW_NEW_TMPL_PANEL); + this->hscroll->SetCapacity(template_panel->current_x); + + NWidgetCore *info_panel = this->GetWidget(TCW_INFO_PANEL); + this->vscroll->SetCapacity(info_panel->current_y); + } + + + virtual void OnInvalidateData(int data = 0, bool gui_scope = true) + { + virtualTrainChangedNotice = true; + UpdateButtonState(); + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + switch(widget) { + case TCW_NEW_TMPL_PANEL: { + NWidgetBase *nwi = this->GetWidget(TCW_NEW_TMPL_PANEL); + ClickedOnVehiclePanel(pt.x - nwi->pos_x, pt.y - nwi->pos_y); + break; + } + case TCW_NEW: { + ShowBuildVirtualTrainWindow(&virtual_train, &virtualTrainChangedNotice); + break; + } + case TCW_CLONE: { + this->SetWidgetDirty(TCW_CLONE); + this->ToggleWidgetLoweredState(TCW_CLONE); + if (this->IsWidgetLowered(TCW_CLONE)) { + SetObjectToPlaceWnd(SPR_CURSOR_CLONE_TRAIN, PAL_NONE, HT_VEHICLE, this); + } else { + ResetObjectToPlace(); + } + break; + } + case TCW_OK: { + uint32 templateIndex = (editTemplate != NULL) ? editTemplate->index : INVALID_VEHICLE; + + if (virtual_train != NULL) { + DoCommandP(0, templateIndex, virtual_train->index, CMD_REPLACE_TEMPLATE_VEHICLE); + virtual_train = NULL; + } else if (templateIndex != INVALID_VEHICLE) { + DoCommandP(0, templateIndex, 0, CMD_DELETE_TEMPLATE_VEHICLE); + } + delete this; + break; + } + case TCW_CANCEL: { + delete this; + break; + } + case TCW_REFIT: { + if (virtual_train != NULL) { + ShowVehicleRefitWindow(virtual_train, INVALID_VEH_ORDER_ID, this, false, true); + } + break; + } + } + } + + virtual bool OnVehicleSelect(const Vehicle *v) + { + // throw away the current virtual train + if (virtual_train != NULL) { + DoCommandP(0, virtual_train->index, 0, CMD_DELETE_VIRTUAL_TRAIN); + virtual_train = NULL; + } + + // create a new one + DoCommandP(0, v->index, 0, CMD_VIRTUAL_TRAIN_FROM_TRAIN, CcSetVirtualTrain); + this->ToggleWidgetLoweredState(TCW_CLONE); + ResetObjectToPlace(); + this->SetDirty(); + + return true; + } + + virtual void DrawWidget(const Rect &r, int widget) const + { + switch(widget) { + case TCW_NEW_TMPL_PANEL: { + if (this->virtual_train) { + DrawTrainImage(virtual_train, r.left+TRAIN_FRONT_SPACE, r.right - 25, r.top + 2, this->sel, EIT_PURCHASE, this->hscroll->GetPosition(), this->vehicle_over); + SetDParam(0, CeilDiv(virtual_train->gcache.cached_total_length * 10, TILE_SIZE)); + SetDParam(1, 1); + DrawString(r.left, r.right, r.top, STR_TINY_BLACK_DECIMAL, TC_BLACK, SA_RIGHT); + } + break; + } + case TCW_INFO_PANEL: { + if (this->virtual_train) { + DrawPixelInfo tmp_dpi, *old_dpi; + + if (!FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.right - r.left, r.bottom - r.top)) break; + + old_dpi = _cur_dpi; + _cur_dpi = &tmp_dpi; + + /* Draw vehicle performance info */ + const GroundVehicleCache *gcache = this->virtual_train->GetGroundVehicleCache(); + SetDParam(2, this->virtual_train->GetDisplayMaxSpeed()); + SetDParam(1, gcache->cached_power); + SetDParam(0, gcache->cached_weight); + SetDParam(3, gcache->cached_max_te / 1000); + DrawString(8, r.right, 4 - this->vscroll->GetPosition(), STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE); + /* Draw cargo summary */ + CargoArray cargo_caps; + for (const Train *tmp = this->virtual_train; tmp != NULL; tmp = tmp->Next()) { + cargo_caps[tmp->cargo_type] += tmp->cargo_cap; + } + int y = 30 - this->vscroll->GetPosition(); + for (CargoID i = 0; i < NUM_CARGO; ++i) { + if (cargo_caps[i] > 0) { + SetDParam(0, i); + SetDParam(1, cargo_caps[i]); + DrawString(8, r.right, y, STR_TMPL_CARGO_SUMMARY, TC_LIGHT_BLUE, SA_LEFT); + y += this->line_height / 3; + } + } + + _cur_dpi = old_dpi; + } + break; + } + default: + break; + } + } + + virtual void OnTick() + { + if (virtualTrainChangedNotice) { + this->SetDirty(); + virtualTrainChangedNotice = false; + UpdateButtonState(); + } + } + + virtual void OnDragDrop(Point pt, int widget) + { + switch (widget) { + case TCW_NEW_TMPL_PANEL: { + const Vehicle *v = NULL; + VehicleID sel = this->sel; + + this->sel = INVALID_VEHICLE; + this->SetDirty(); + + NWidgetBase *nwi = this->GetWidget(TCW_NEW_TMPL_PANEL); + GetDepotVehiclePtData gdvp = { NULL, NULL }; + + if (this->GetVehicleFromDepotWndPt(pt.x - nwi->pos_x, pt.y - nwi->pos_y, &v, &gdvp) == MODE_DRAG_VEHICLE && sel != INVALID_VEHICLE) { + if (gdvp.wagon == NULL || gdvp.wagon->index != sel) { + this->vehicle_over = INVALID_VEHICLE; + TrainDepotMoveVehicle(gdvp.wagon, sel, gdvp.head); + } + } + break; + } + case TCW_SELL_TMPL: { + if (this->IsWidgetDisabled(widget)) return; + if (this->sel == INVALID_VEHICLE) return; + + int sell_cmd = (_ctrl_pressed) ? 1 : 0; + + Train* train_to_delete = Train::Get(this->sel); + + if (virtual_train == train_to_delete) { + virtual_train = (_ctrl_pressed) ? NULL : virtual_train->GetNextUnit(); + } + + DoCommandP(0, this->sel | (sell_cmd << 20) | (1 << 21), 0, GetCmdSellVeh(VEH_TRAIN)); + + this->sel = INVALID_VEHICLE; + + this->SetDirty(); + UpdateButtonState(); + break; + } + default: + this->sel = INVALID_VEHICLE; + this->SetDirty(); + break; + } + _cursor.vehchain = false; + this->sel = INVALID_VEHICLE; + this->SetDirty(); + } + + virtual void OnMouseDrag(Point pt, int widget) + { + if (this->sel == INVALID_VEHICLE) return; + /* A rail vehicle is dragged.. */ + if (widget != TCW_NEW_TMPL_PANEL) { // ..outside of the depot matrix. + if (this->vehicle_over != INVALID_VEHICLE) { + this->vehicle_over = INVALID_VEHICLE; + this->SetWidgetDirty(TCW_NEW_TMPL_PANEL); + } + return; + } + + NWidgetBase *matrix = this->GetWidget(widget); + const Vehicle *v = NULL; + GetDepotVehiclePtData gdvp = {NULL, NULL}; + + if (this->GetVehicleFromDepotWndPt(pt.x - matrix->pos_x, pt.y - matrix->pos_y, &v, &gdvp) != MODE_DRAG_VEHICLE) return; + VehicleID new_vehicle_over = INVALID_VEHICLE; + if (gdvp.head != NULL) { + if (gdvp.wagon == NULL && gdvp.head->Last()->index != this->sel) { // ..at the end of the train. + /* NOTE: As a wagon can't be moved at the begin of a train, head index isn't used to mark a drag-and-drop + * destination inside a train. This head index is then used to indicate that a wagon is inserted at + * the end of the train. + */ + new_vehicle_over = gdvp.head->index; + } else if (gdvp.wagon != NULL && gdvp.head != gdvp.wagon && + gdvp.wagon->index != this->sel && + gdvp.wagon->Previous()->index != this->sel) { // ..over an existing wagon. + new_vehicle_over = gdvp.wagon->index; + } + } + if (this->vehicle_over == new_vehicle_over) return; + + this->vehicle_over = new_vehicle_over; + this->SetWidgetDirty(widget); + } + + virtual void OnPaint() + { + uint min_width = 32; + uint min_height = 30; + uint width = 0; + uint height = 30; + CargoArray cargo_caps; + + if (virtual_train != NULL) { + for (Train *train = virtual_train; train != NULL; train = train->Next()) { + width += train->GetDisplayImageWidth(); + cargo_caps[train->cargo_type] += train->cargo_cap; + } + + for (CargoID i = 0; i < NUM_CARGO; ++i) { + if (cargo_caps[i] > 0) { + height += this->line_height / 3; + } + } + } + + min_width = max(min_width, width); + this->hscroll->SetCount(min_width + 50); + + min_height = max(min_height, height); + this->vscroll->SetCount(min_height); + + this->DrawWidgets(); + } + + struct GetDepotVehiclePtData { + const Vehicle *head; + const Vehicle *wagon; + }; + + enum DepotGUIAction { + MODE_ERROR, + MODE_DRAG_VEHICLE, + MODE_SHOW_VEHICLE, + MODE_START_STOP, + }; + + uint count_width; + uint header_width; + + DepotGUIAction GetVehicleFromDepotWndPt(int x, int y, const Vehicle **veh, GetDepotVehiclePtData *d) const + { + const NWidgetCore *matrix_widget = this->GetWidget(TCW_NEW_TMPL_PANEL); + /* In case of RTL the widgets are swapped as a whole */ + if (_current_text_dir == TD_RTL) x = matrix_widget->current_x - x; + + x -= TRAIN_FRONT_SPACE; + + uint xm = x; + + bool wagon = false; + + x += this->hscroll->GetPosition(); + const Train *v = virtual_train; + d->head = d->wagon = v; + + if (xm <= this->header_width) { + if (wagon) return MODE_ERROR; + + return MODE_SHOW_VEHICLE; + } + + /* Account for the header */ + x -= this->header_width; + + /* find the vehicle in this row that was clicked */ + for (; v != NULL; v = v->Next()) { + x -= v->GetDisplayImageWidth(); + if (x < 0) break; + } + + d->wagon = (v != NULL ? v->GetFirstEnginePart() : NULL); + + return MODE_DRAG_VEHICLE; + } + + void ClickedOnVehiclePanel(int x, int y) + { + GetDepotVehiclePtData gdvp = { NULL, NULL }; + const Vehicle *v = NULL; + this->GetVehicleFromDepotWndPt(x, y, &v, &gdvp); + + v = gdvp.wagon; + + if (v != NULL && VehicleClicked(v)) return; + VehicleID sel = this->sel; + + if (sel != INVALID_VEHICLE) { + this->sel = INVALID_VEHICLE; + TrainDepotMoveVehicle(v, sel, gdvp.head); + } else if (v != NULL) { + int image = v->GetImage(_current_text_dir == TD_RTL ? DIR_E : DIR_W, EIT_PURCHASE); + SetObjectToPlaceWnd(image, GetVehiclePalette(v), HT_DRAG, this); + + this->sel = v->index; + this->SetDirty(); + + _cursor.short_vehicle_offset = v->IsGroundVehicle() ? 16 - v->GetGroundVehicleCache()->cached_veh_length * 2 : 0; + _cursor.vehchain = _ctrl_pressed; + } + } + + void RearrangeVirtualTrain() + { + virtual_train = virtual_train->First(); + } + + + void UpdateButtonState() + { + this->SetWidgetDisabledState(TCW_REFIT, virtual_train == NULL); + } +}; + +void ShowTemplateCreateWindow(TemplateVehicle *to_edit, bool *noticeParent, bool *createWindowOpen, int step_h) +{ + if (BringWindowToFrontById(WC_CREATE_TEMPLATE, VEH_TRAIN) != NULL) return; + new TemplateCreateWindow(&_template_create_window_desc, to_edit, noticeParent, createWindowOpen, step_h); +} + +void CcSetVirtualTrain(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) +{ + if (result.Failed()) return; + + Window* window = FindWindowById(WC_CREATE_TEMPLATE, 0); + if (window) { + Train* train = Train::From(Vehicle::Get(_new_vehicle_id)); + ((TemplateCreateWindow*)window)->SetVirtualTrain(train); + window->InvalidateData(); + } +} + +void CcVirtualTrainWaggonsMoved(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) +{ + if (result.Failed()) return; + + Window* window = FindWindowById(WC_CREATE_TEMPLATE, 0); + if (window) { + ((TemplateCreateWindow*)window)->RearrangeVirtualTrain(); + window->InvalidateData(); + } +} + +void CcDeleteVirtualTrain(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) +{ + VehicleID virtual_train_id = p2; + DoCommandP(0, virtual_train_id, 0, CMD_DELETE_VIRTUAL_TRAIN); +} diff --git a/src/tbtr_template_gui_create.h b/src/tbtr_template_gui_create.h new file mode 100644 index 0000000000..d95cf53bdf --- /dev/null +++ b/src/tbtr_template_gui_create.h @@ -0,0 +1,20 @@ +/* $Id$ */ + +/* + * 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_gui_create.h Template-based train replacement: template creation GUI header. */ + +#ifndef TEMPLATE_GUI_CREATE +#define TEMPLATE_GUI_CREATE + +#include "tbtr_template_vehicle.h" +#include "tbtr_template_gui_create_virtualtrain.h" + +void ShowTemplateCreateWindow(TemplateVehicle*, bool*, bool*, int); + +#endif diff --git a/src/tbtr_template_gui_create_virtualtrain.cpp b/src/tbtr_template_gui_create_virtualtrain.cpp new file mode 100644 index 0000000000..de3f537327 --- /dev/null +++ b/src/tbtr_template_gui_create_virtualtrain.cpp @@ -0,0 +1,837 @@ +/* $Id$ */ + +/* + * 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_gui_create_virtualtrain.cpp Template-based train replacement: template creation vehicle build GUI. */ + +#include "stdafx.h" +#include "engine_base.h" +#include "engine_func.h" +#include "station_base.h" +#include "articulated_vehicles.h" +#include "textbuf_gui.h" +#include "command_func.h" +#include "company_func.h" +#include "vehicle_gui.h" +#include "newgrf_engine.h" +#include "newgrf_text.h" +#include "group.h" +#include "string_func.h" +#include "strings_func.h" +#include "window_func.h" +#include "date_func.h" +#include "vehicle_func.h" +#include "widgets/dropdown_func.h" +#include "engine_gui.h" +#include "cargotype.h" +#include "core/geometry_func.hpp" +#include "vehicle_gui.h" +#include "tbtr_template_gui_create_virtualtrain.h" + +#include "widgets/build_vehicle_widget.h" + +#include "table/strings.h" + +#include "safeguards.h" + +static const NWidgetPart _nested_build_vehicle_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, WID_BV_CAPTION), SetDataTip(STR_WHITE_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_SHADEBOX, COLOUR_GREY), + NWidget(WWT_STICKYBOX, COLOUR_GREY), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_VERTICAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SORT_ASCENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), + NWidget(NWID_SPACER), SetFill(1, 1), + EndContainer(), + NWidget(NWID_VERTICAL), + NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_SORT_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA), + NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_CARGO_FILTER_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_FILTER_CRITERIA), + EndContainer(), + EndContainer(), + EndContainer(), + /* Vehicle list. */ + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, WID_BV_LIST), SetResize(1, 1), SetFill(1, 0), SetDataTip(0x101, STR_NULL), SetScrollbar(WID_BV_SCROLLBAR), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BV_SCROLLBAR), + EndContainer(), + /* Panel with details. */ + NWidget(WWT_PANEL, COLOUR_GREY, WID_BV_PANEL), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(), + /* Build/rename buttons, resize button. */ + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_BUILD), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_TMPL_CONFIRM, STR_TMPL_CONFIRM), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_RENAME), SetResize(1, 0), SetFill(1, 0), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), +}; + +/** Special cargo filter criteria */ +static const CargoID CF_ANY = CT_NO_REFIT; ///< Show all vehicles independent of carried cargo (i.e. no filtering) +static const CargoID CF_NONE = CT_INVALID; ///< Show only vehicles which do not carry cargo (e.g. train engines) + +static bool _internal_sort_order; ///< false = descending, true = ascending +static byte _last_sort_criteria[] = {0, 0, 0, 0}; +static bool _last_sort_order[] = {false, false, false, false}; +static CargoID _last_filter_criteria[] = {CF_ANY, CF_ANY, CF_ANY, CF_ANY}; + +/** + * Determines order of engines by engineID + * @param *a first engine to compare + * @param *b second engine to compare + * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal + */ +static int CDECL EngineNumberSorter(const EngineID *a, const EngineID *b) +{ + int r = Engine::Get(*a)->list_position - Engine::Get(*b)->list_position; + + return _internal_sort_order ? -r : r; +} + +/** + * Determines order of engines by introduction date + * @param *a first engine to compare + * @param *b second engine to compare + * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal + */ +static int CDECL EngineIntroDateSorter(const EngineID *a, const EngineID *b) +{ + const int va = Engine::Get(*a)->intro_date; + const int vb = Engine::Get(*b)->intro_date; + const int r = va - vb; + + /* Use EngineID to sort instead since we want consistent sorting */ + if (r == 0) return EngineNumberSorter(a, b); + return _internal_sort_order ? -r : r; +} + +/** + * Determines order of engines by name + * @param *a first engine to compare + * @param *b second engine to compare + * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal + */ +static int CDECL EngineNameSorter(const EngineID *a, const EngineID *b) +{ + static EngineID last_engine[2] = { INVALID_ENGINE, INVALID_ENGINE }; + static char last_name[2][64] = { "\0", "\0" }; + + const EngineID va = *a; + const EngineID vb = *b; + + if (va != last_engine[0]) { + last_engine[0] = va; + SetDParam(0, va); + GetString(last_name[0], STR_ENGINE_NAME, lastof(last_name[0])); + } + + if (vb != last_engine[1]) { + last_engine[1] = vb; + SetDParam(0, vb); + GetString(last_name[1], STR_ENGINE_NAME, lastof(last_name[1])); + } + + int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting). + + /* Use EngineID to sort instead since we want consistent sorting */ + if (r == 0) return EngineNumberSorter(a, b); + return _internal_sort_order ? -r : r; +} + +/** + * Determines order of engines by reliability + * @param *a first engine to compare + * @param *b second engine to compare + * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal + */ +static int CDECL EngineReliabilitySorter(const EngineID *a, const EngineID *b) +{ + const int va = Engine::Get(*a)->reliability; + const int vb = Engine::Get(*b)->reliability; + const int r = va - vb; + + /* Use EngineID to sort instead since we want consistent sorting */ + if (r == 0) return EngineNumberSorter(a, b); + return _internal_sort_order ? -r : r; +} + +/** + * Determines order of engines by purchase cost + * @param *a first engine to compare + * @param *b second engine to compare + * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal + */ +static int CDECL EngineCostSorter(const EngineID *a, const EngineID *b) +{ + Money va = Engine::Get(*a)->GetCost(); + Money vb = Engine::Get(*b)->GetCost(); + int r = ClampToI32(va - vb); + + /* Use EngineID to sort instead since we want consistent sorting */ + if (r == 0) return EngineNumberSorter(a, b); + return _internal_sort_order ? -r : r; +} + +/** + * Determines order of engines by speed + * @param *a first engine to compare + * @param *b second engine to compare + * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal + */ +static int CDECL EngineSpeedSorter(const EngineID *a, const EngineID *b) +{ + int va = Engine::Get(*a)->GetDisplayMaxSpeed(); + int vb = Engine::Get(*b)->GetDisplayMaxSpeed(); + int r = va - vb; + + /* Use EngineID to sort instead since we want consistent sorting */ + if (r == 0) return EngineNumberSorter(a, b); + return _internal_sort_order ? -r : r; +} + +/** + * Determines order of engines by power + * @param *a first engine to compare + * @param *b second engine to compare + * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal + */ +static int CDECL EnginePowerSorter(const EngineID *a, const EngineID *b) +{ + int va = Engine::Get(*a)->GetPower(); + int vb = Engine::Get(*b)->GetPower(); + int r = va - vb; + + /* Use EngineID to sort instead since we want consistent sorting */ + if (r == 0) return EngineNumberSorter(a, b); + return _internal_sort_order ? -r : r; +} + +/** + * Determines order of engines by tractive effort + * @param *a first engine to compare + * @param *b second engine to compare + * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal + */ +static int CDECL EngineTractiveEffortSorter(const EngineID *a, const EngineID *b) +{ + int va = Engine::Get(*a)->GetDisplayMaxTractiveEffort(); + int vb = Engine::Get(*b)->GetDisplayMaxTractiveEffort(); + int r = va - vb; + + /* Use EngineID to sort instead since we want consistent sorting */ + if (r == 0) return EngineNumberSorter(a, b); + return _internal_sort_order ? -r : r; +} + +/** + * Determines order of engines by running costs + * @param *a first engine to compare + * @param *b second engine to compare + * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal + */ +static int CDECL EngineRunningCostSorter(const EngineID *a, const EngineID *b) +{ + Money va = Engine::Get(*a)->GetRunningCost(); + Money vb = Engine::Get(*b)->GetRunningCost(); + int r = ClampToI32(va - vb); + + /* Use EngineID to sort instead since we want consistent sorting */ + if (r == 0) return EngineNumberSorter(a, b); + return _internal_sort_order ? -r : r; +} + +/** + * Determines order of engines by running costs + * @param *a first engine to compare + * @param *b second engine to compare + * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal + */ +static int CDECL EnginePowerVsRunningCostSorter(const EngineID *a, const EngineID *b) +{ + const Engine *e_a = Engine::Get(*a); + const Engine *e_b = Engine::Get(*b); + + /* Here we are using a few tricks to get the right sort. + * We want power/running cost, but since we usually got higher running cost than power and we store the result in an int, + * we will actually calculate cunning cost/power (to make it more than 1). + * Because of this, the return value have to be reversed as well and we return b - a instead of a - b. + * Another thing is that both power and running costs should be doubled for multiheaded engines. + * Since it would be multipling with 2 in both numerator and denumerator, it will even themselves out and we skip checking for multiheaded. */ + Money va = (e_a->GetRunningCost()) / max(1U, (uint)e_a->GetPower()); + Money vb = (e_b->GetRunningCost()) / max(1U, (uint)e_b->GetPower()); + int r = ClampToI32(vb - va); + + /* Use EngineID to sort instead since we want consistent sorting */ + if (r == 0) return EngineNumberSorter(a, b); + return _internal_sort_order ? -r : r; +} + +/* Train sorting functions */ + +/** + * Determines order of train engines by capacity + * @param *a first engine to compare + * @param *b second engine to compare + * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal + */ +static int CDECL TrainEngineCapacitySorter(const EngineID *a, const EngineID *b) +{ + const RailVehicleInfo *rvi_a = RailVehInfo(*a); + const RailVehicleInfo *rvi_b = RailVehInfo(*b); + + int va = GetTotalCapacityOfArticulatedParts(*a) * (rvi_a->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1); + int vb = GetTotalCapacityOfArticulatedParts(*b) * (rvi_b->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1); + int r = va - vb; + + /* Use EngineID to sort instead since we want consistent sorting */ + if (r == 0) return EngineNumberSorter(a, b); + return _internal_sort_order ? -r : r; +} + +/** + * Determines order of train engines by engine / wagon + * @param *a first engine to compare + * @param *b second engine to compare + * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal + */ +static int CDECL TrainEnginesThenWagonsSorter(const EngineID *a, const EngineID *b) +{ + int val_a = (RailVehInfo(*a)->railveh_type == RAILVEH_WAGON ? 1 : 0); + int val_b = (RailVehInfo(*b)->railveh_type == RAILVEH_WAGON ? 1 : 0); + int r = val_a - val_b; + + /* Use EngineID to sort instead since we want consistent sorting */ + if (r == 0) return EngineNumberSorter(a, b); + return _internal_sort_order ? -r : r; +} + +/** Sort functions for the vehicle sort criteria, for each vehicle type. */ +static EngList_SortTypeFunction * const _sorter[][11] = {{ + /* Trains */ + &EngineNumberSorter, + &EngineCostSorter, + &EngineSpeedSorter, + &EnginePowerSorter, + &EngineTractiveEffortSorter, + &EngineIntroDateSorter, + &EngineNameSorter, + &EngineRunningCostSorter, + &EnginePowerVsRunningCostSorter, + &EngineReliabilitySorter, + &TrainEngineCapacitySorter, +}}; + +static const StringID _sort_listing[][12] = {{ + /* Trains */ + STR_SORT_BY_ENGINE_ID, + STR_SORT_BY_COST, + STR_SORT_BY_MAX_SPEED, + STR_SORT_BY_POWER, + STR_SORT_BY_TRACTIVE_EFFORT, + STR_SORT_BY_INTRO_DATE, + STR_SORT_BY_NAME, + STR_SORT_BY_RUNNING_COST, + STR_SORT_BY_POWER_VS_RUNNING_COST, + STR_SORT_BY_RELIABILITY, + STR_SORT_BY_CARGO_CAPACITY, + INVALID_STRING_ID +}}; + +/** Cargo filter functions */ +static bool CDECL CargoFilter(const EngineID *eid, const CargoID cid) +{ + if (cid == CF_ANY) return true; + uint32 refit_mask = GetUnionOfArticulatedRefitMasks(*eid, true); + return (cid == CF_NONE ? refit_mask == 0 : HasBit(refit_mask, cid)); +} + +static GUIEngineList::FilterFunction * const _filter_funcs[] = { + &CargoFilter, +}; + +/** + * Engine drawing loop + * @param type Type of vehicle (VEH_*) + * @param l The left most location of the list + * @param r The right most location of the list + * @param y The top most location of the list + * @param eng_list What engines to draw + * @param min where to start in the list + * @param max where in the list to end + * @param selected_id what engine to highlight as selected, if any + * @param show_count Whether to show the amount of engines or not + * @param selected_group the group to list the engines of + */ +static void DrawEngineList(VehicleType type, int l, int r, int y, const GUIEngineList *eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group) +{ + static const int sprite_widths[] = { 60, 60, 76, 67 }; + static const int sprite_y_offsets[] = { -1, -1, -2, -2 }; + + /* Obligatory sanity checks! */ + assert((uint)type < lengthof(sprite_widths)); + assert_compile(lengthof(sprite_y_offsets) == lengthof(sprite_widths)); + assert(max <= eng_list->Length()); + + bool rtl = _current_text_dir == TD_RTL; + int step_size = GetEngineListHeight(type); + int sprite_width = sprite_widths[type]; + + int sprite_x = (rtl ? r - sprite_width / 2 : l + sprite_width / 2) - 1; + int sprite_y_offset = sprite_y_offsets[type] + step_size / 2; + + int text_left = l + (rtl ? WD_FRAMERECT_LEFT : sprite_width); + int text_right = r - (rtl ? sprite_width : WD_FRAMERECT_RIGHT); + + int normal_text_y_offset = (step_size - FONT_HEIGHT_NORMAL) / 2; + int small_text_y_offset = step_size - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1; + + for (; min < max; min++, y += step_size) { + const EngineID engine = (*eng_list)[min]; + /* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */ + const uint num_engines = GetGroupNumEngines(_local_company, selected_group, engine); + + SetDParam(0, engine); + DrawString(text_left, text_right, y + normal_text_y_offset, STR_ENGINE_NAME, engine == selected_id ? TC_WHITE : TC_BLACK); + DrawVehicleEngine(l, r, sprite_x, y + sprite_y_offset, engine, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(engine, _local_company), EIT_PURCHASE); + if (show_count) { + SetDParam(0, num_engines); + DrawString(text_left, text_right, y + small_text_y_offset, STR_TINY_BLACK_COMA, TC_FROMSTRING, SA_RIGHT); + } + } +} + + +struct BuildVirtualTrainWindow : Window { + VehicleType vehicle_type; + union { + RailTypeByte railtype; + RoadTypes roadtypes; + } filter; + bool descending_sort_order; + byte sort_criteria; + bool listview_mode; + EngineID sel_engine; + EngineID rename_engine; + GUIEngineList eng_list; + CargoID cargo_filter[NUM_CARGO + 2]; ///< Available cargo filters; CargoID or CF_ANY or CF_NONE + StringID cargo_filter_texts[NUM_CARGO + 3]; ///< Texts for filter_cargo, terminated by INVALID_STRING_ID + byte cargo_filter_criteria; ///< Selected cargo filter + int details_height; ///< Minimal needed height of the details panels (found so far). + Scrollbar *vscroll; + Train **virtual_train; ///< the virtual train that is currently being created + bool *noticeParent; + + BuildVirtualTrainWindow(WindowDesc *desc, Train **vt, bool *notice) : Window(desc) + { + this->vehicle_type = VEH_TRAIN; + this->window_number = 0; + + this->sel_engine = INVALID_ENGINE; + + this->sort_criteria = _last_sort_criteria[VEH_TRAIN]; + this->descending_sort_order = _last_sort_order[VEH_TRAIN]; + + this->filter.railtype = RAILTYPE_END; + + this->listview_mode = (this->window_number <= VEH_END); + + this->CreateNestedTree(desc); + + this->vscroll = this->GetScrollbar(WID_BV_SCROLLBAR); + + NWidgetCore *widget = this->GetWidget(WID_BV_LIST); + + widget = this->GetWidget(WID_BV_BUILD); + + widget = this->GetWidget(WID_BV_RENAME); + widget->widget_data = STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON + VEH_TRAIN; + widget->tool_tip = STR_BUY_VEHICLE_TRAIN_RENAME_TOOLTIP + VEH_TRAIN; + + this->details_height = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9) * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM; + + this->FinishInitNested(VEH_TRAIN); + + this->owner = _local_company; + + this->eng_list.ForceRebuild(); + this->GenerateBuildList(); + + if (this->eng_list.Length() > 0) this->sel_engine = this->eng_list[0]; + + this->virtual_train = vt; + this->noticeParent = notice; + } + + /** Populate the filter list and set the cargo filter criteria. */ + void SetCargoFilterArray() + { + uint filter_items = 0; + + /* Add item for disabling filtering. */ + this->cargo_filter[filter_items] = CF_ANY; + this->cargo_filter_texts[filter_items] = STR_PURCHASE_INFO_ALL_TYPES; + filter_items++; + + /* Add item for vehicles not carrying anything, e.g. train engines. + * This could also be useful for eyecandy vehicles of other types, but is likely too confusing for joe, */ + if (this->vehicle_type == VEH_TRAIN) { + this->cargo_filter[filter_items] = CF_NONE; + this->cargo_filter_texts[filter_items] = STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE; + filter_items++; + } + + /* Collect available cargo types for filtering. */ + const CargoSpec *cs; + FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + this->cargo_filter[filter_items] = cs->Index(); + this->cargo_filter_texts[filter_items] = cs->name; + filter_items++; + } + + /* Terminate the filter list. */ + this->cargo_filter_texts[filter_items] = INVALID_STRING_ID; + + /* If not found, the cargo criteria will be set to all cargoes. */ + this->cargo_filter_criteria = 0; + + /* Find the last cargo filter criteria. */ + for (uint i = 0; i < filter_items; ++i) { + if (this->cargo_filter[i] == _last_filter_criteria[this->vehicle_type]) { + this->cargo_filter_criteria = i; + break; + } + } + + this->eng_list.SetFilterFuncs(_filter_funcs); + this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY); + } + + void OnInit() + { + this->SetCargoFilterArray(); + } + + /** Filter the engine list against the currently selected cargo filter */ + void FilterEngineList() + { + this->eng_list.Filter(this->cargo_filter[this->cargo_filter_criteria]); + if (0 == this->eng_list.Length()) { // no engine passed through the filter, invalidate the previously selected engine + this->sel_engine = INVALID_ENGINE; + } else if (!this->eng_list.Contains(this->sel_engine)) { // previously selected engine didn't pass the filter, select the first engine of the list + this->sel_engine = this->eng_list[0]; + } + } + + /** Filter a single engine */ + bool FilterSingleEngine(EngineID eid) + { + CargoID filter_type = this->cargo_filter[this->cargo_filter_criteria]; + return (filter_type == CF_ANY || CargoFilter(&eid, filter_type)); + } + + /* Figure out what train EngineIDs to put in the list */ + void GenerateBuildTrainList() + { + EngineID sel_id = INVALID_ENGINE; + int num_engines = 0; + int num_wagons = 0; + + this->filter.railtype = (this->listview_mode) ? RAILTYPE_END : GetRailType(this->window_number); + + this->eng_list.Clear(); + + /* Make list of all available train engines and wagons. + * Also check to see if the previously selected engine is still available, + * and if not, reset selection to INVALID_ENGINE. This could be the case + * when engines become obsolete and are removed */ + const Engine *e; + FOR_ALL_ENGINES_OF_TYPE(e, VEH_TRAIN) { + EngineID eid = e->index; + const RailVehicleInfo *rvi = &e->u.rail; + + if (this->filter.railtype != RAILTYPE_END && !HasPowerOnRail(rvi->railtype, this->filter.railtype)) continue; + if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue; + + /* Filter now! So num_engines and num_wagons is valid */ + if (!FilterSingleEngine(eid)) continue; + + *this->eng_list.Append() = eid; + + if (rvi->railveh_type != RAILVEH_WAGON) { + num_engines++; + } else { + num_wagons++; + } + + if (eid == this->sel_engine) sel_id = eid; + } + + this->sel_engine = sel_id; + + /* make engines first, and then wagons, sorted by ListPositionOfEngine() */ + _internal_sort_order = false; + EngList_Sort(&this->eng_list, TrainEnginesThenWagonsSorter); + + /* and then sort engines */ + _internal_sort_order = this->descending_sort_order; + EngList_SortPartial(&this->eng_list, _sorter[0][this->sort_criteria], 0, num_engines); + + /* and finally sort wagons */ + EngList_SortPartial(&this->eng_list, _sorter[0][this->sort_criteria], num_engines, num_wagons); + } + + /* Generate the list of vehicles */ + void GenerateBuildList() + { + if (!this->eng_list.NeedRebuild()) return; + + this->GenerateBuildTrainList(); + this->eng_list.Compact(); + this->eng_list.RebuildDone(); + return; // trains should not reach the last sorting + + + this->FilterEngineList(); + + _internal_sort_order = this->descending_sort_order; + EngList_Sort(&this->eng_list, _sorter[this->vehicle_type][this->sort_criteria]); + + this->eng_list.Compact(); + this->eng_list.RebuildDone(); + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + switch (widget) { + case WID_BV_SORT_ASCENDING_DESCENDING: + this->descending_sort_order ^= true; + _last_sort_order[this->vehicle_type] = this->descending_sort_order; + this->eng_list.ForceRebuild(); + this->SetDirty(); + break; + + case WID_BV_LIST: { + uint i = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BV_LIST); + size_t num_items = this->eng_list.Length(); + this->sel_engine = (i < num_items) ? this->eng_list[i] : INVALID_ENGINE; + this->SetDirty(); + if (click_count > 1 && !this->listview_mode) this->OnClick(pt, WID_BV_BUILD, 1); + break; + } + case WID_BV_SORT_DROPDOWN: { // Select sorting criteria dropdown menu + uint32 hidden_mask = 0; + /* Disable sorting by power or tractive effort when the original acceleration model for road vehicles is being used. */ + if (this->vehicle_type == VEH_ROAD && + _settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL) { + SetBit(hidden_mask, 3); // power + SetBit(hidden_mask, 4); // tractive effort + SetBit(hidden_mask, 8); // power by running costs + } + /* Disable sorting by tractive effort when the original acceleration model for trains is being used. */ + if (this->vehicle_type == VEH_TRAIN && + _settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) { + SetBit(hidden_mask, 4); // tractive effort + } + ShowDropDownMenu(this, _sort_listing[this->vehicle_type], this->sort_criteria, WID_BV_SORT_DROPDOWN, 0, hidden_mask); + break; + } + + case WID_BV_CARGO_FILTER_DROPDOWN: // Select cargo filtering criteria dropdown menu + ShowDropDownMenu(this, this->cargo_filter_texts, this->cargo_filter_criteria, WID_BV_CARGO_FILTER_DROPDOWN, 0, 0); + break; + + case WID_BV_BUILD: { + EngineID sel_eng = this->sel_engine; + if (sel_eng != INVALID_ENGINE) { + DoCommandP(0, sel_engine, 0, CMD_BUILD_VIRTUAL_RAIL_VEHICLE, CcAddVirtualEngine); + } + break; + } + } + } + + /** + * Some data on this window has become invalid. + * @param data Information about the changed data. + * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details. + */ + virtual void OnInvalidateData(int data = 0, bool gui_scope = true) + { + if (!gui_scope) return; + /* When switching to original acceleration model for road vehicles, clear the selected sort criteria if it is not available now. */ + + this->eng_list.ForceRebuild(); + } + + virtual void SetStringParameters(int widget) const + { + switch (widget) { + case WID_BV_CAPTION: + if (this->vehicle_type == VEH_TRAIN && !this->listview_mode) { + const RailtypeInfo *rti = GetRailTypeInfo(this->filter.railtype); + SetDParam(0, rti->strings.build_caption); + } else { + SetDParam(0, (this->listview_mode ? STR_VEHICLE_LIST_AVAILABLE_TRAINS : STR_BUY_VEHICLE_TRAIN_ALL_CAPTION) + this->vehicle_type); + } + break; + + case WID_BV_SORT_DROPDOWN: + SetDParam(0, _sort_listing[this->vehicle_type][this->sort_criteria]); + break; + + case WID_BV_CARGO_FILTER_DROPDOWN: + SetDParam(0, this->cargo_filter_texts[this->cargo_filter_criteria]); + break; + } + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + switch (widget) { + case WID_BV_LIST: + resize->height = GetEngineListHeight(this->vehicle_type); + size->height = 3 * resize->height; + break; + + case WID_BV_PANEL: + size->height = this->details_height; + break; + + case WID_BV_SORT_ASCENDING_DESCENDING: { + Dimension d = GetStringBoundingBox(this->GetWidget(widget)->widget_data); + d.width += padding.width + WD_CLOSEBOX_WIDTH * 2; // Doubled since the string is centred and it also looks better. + d.height += padding.height; + *size = maxdim(*size, d); + break; + } + } + } + + virtual void DrawWidget(const Rect &r, int widget) const + { + switch (widget) { + case WID_BV_LIST: + DrawEngineList(this->vehicle_type, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, + &this->eng_list, this->vscroll->GetPosition(), min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), + this->eng_list.Length()), this->sel_engine, false, DEFAULT_GROUP); + break; + + case WID_BV_SORT_ASCENDING_DESCENDING: + this->DrawSortButtonState(WID_BV_SORT_ASCENDING_DESCENDING, this->descending_sort_order ? SBS_DOWN : SBS_UP); + break; + } + } + + virtual void OnPaint() + { + this->GenerateBuildList(); + this->vscroll->SetCount(this->eng_list.Length()); + + this->DrawWidgets(); + + if (!this->IsShaded()) { + int needed_height = this->details_height; + /* Draw details panels. */ + if (this->sel_engine != INVALID_ENGINE) { + NWidgetBase *nwi = this->GetWidget(WID_BV_PANEL); + int text_end = DrawVehiclePurchaseInfo(nwi->pos_x + WD_FRAMETEXT_LEFT, nwi->pos_x + nwi->current_x - WD_FRAMETEXT_RIGHT, + nwi->pos_y + WD_FRAMERECT_TOP, this->sel_engine); + needed_height = max(needed_height, text_end - (int)nwi->pos_y + WD_FRAMERECT_BOTTOM); + } + if (needed_height != this->details_height) { // Details window are not high enough, enlarge them. + int resize = needed_height - this->details_height; + this->details_height = needed_height; + this->ReInit(0, resize); + return; + } + } + } + + virtual void OnQueryTextFinished(char *str) + { + if (str == NULL) return; + + DoCommandP(0, this->rename_engine, 0, CMD_RENAME_ENGINE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type), NULL, str); + } + + virtual void OnDropdownSelect(int widget, int index) + { + switch (widget) { + case WID_BV_SORT_DROPDOWN: + if (this->sort_criteria != index) { + this->sort_criteria = index; + _last_sort_criteria[this->vehicle_type] = this->sort_criteria; + this->eng_list.ForceRebuild(); + } + break; + + case WID_BV_CARGO_FILTER_DROPDOWN: // Select a cargo filter criteria + if (this->cargo_filter_criteria != index) { + this->cargo_filter_criteria = index; + _last_filter_criteria[this->vehicle_type] = this->cargo_filter[this->cargo_filter_criteria]; + /* deactivate filter if criteria is 'Show All', activate it otherwise */ + this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY); + this->eng_list.ForceRebuild(); + } + break; + } + this->SetDirty(); + } + + virtual void OnResize() + { + this->vscroll->SetCapacityFromWidget(this, WID_BV_LIST); + this->GetWidget(WID_BV_LIST)->widget_data = (this->vscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START); + } + + void AddVirtualEngine(Train *toadd) + { + if (*virtual_train == NULL) { + *virtual_train = toadd; + } else { + VehicleID target = (*(this->virtual_train))->GetLastUnit()->index; + + DoCommandP(0, (1 << 21) | toadd->index, target, CMD_MOVE_RAIL_VEHICLE); + } + *noticeParent = true; + } +}; + +void CcAddVirtualEngine(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) +{ + if (result.Failed()) return; + + Window* window = FindWindowById(WC_BUILD_VIRTUAL_TRAIN, 0); + if (window) { + Train* train = Train::From(Vehicle::Get(_new_vehicle_id)); + ((BuildVirtualTrainWindow*)window)->AddVirtualEngine(train); + window->InvalidateData(); + } +} + +static WindowDesc _build_vehicle_desc( + WDP_AUTO, // window position + "template create virtual train", // const char* ini_key + 240, 268, // window size + WC_BUILD_VIRTUAL_TRAIN, // window class + WC_CREATE_TEMPLATE, // parent window class + WDF_CONSTRUCTION, // window flags + _nested_build_vehicle_widgets, lengthof(_nested_build_vehicle_widgets) // widgets + num widgets +); + +void ShowBuildVirtualTrainWindow(Train **vt, bool *noticeParent) +{ + // '0' as in VEH_TRAIN = Tile=0 + assert(IsCompanyBuildableVehicleType(VEH_TRAIN)); + + DeleteWindowById(WC_BUILD_VEHICLE, 0); + + new BuildVirtualTrainWindow(&_build_vehicle_desc, vt, noticeParent); +} diff --git a/src/tbtr_template_gui_create_virtualtrain.h b/src/tbtr_template_gui_create_virtualtrain.h new file mode 100644 index 0000000000..ee0b4123c3 --- /dev/null +++ b/src/tbtr_template_gui_create_virtualtrain.h @@ -0,0 +1,19 @@ +/* $Id$ */ + +/* + * 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_gui_create_virtualtrain.cpp Template-based train replacement: template creation vehicle build GUI header. */ + +#ifndef BUILD_VIRTUAL_TRAIN_GUI +#define BUILD_VIRTUAL_TRAIN_GUI + +#include "train.h" + +void ShowBuildVirtualTrainWindow(Train**, bool*); + +#endif diff --git a/src/tbtr_template_gui_main.cpp b/src/tbtr_template_gui_main.cpp new file mode 100644 index 0000000000..afd63da94c --- /dev/null +++ b/src/tbtr_template_gui_main.cpp @@ -0,0 +1,803 @@ +/* $Id$ */ + +/* + * 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_gui_main.cpp Template-based train replacement: main GUI. */ + +#include "stdafx.h" +#include "command_func.h" +#include "vehicle_gui.h" +#include "newgrf_engine.h" +#include "group.h" +#include "rail.h" +#include "strings_func.h" +#include "window_func.h" +#include "autoreplace_func.h" +#include "company_func.h" +#include "engine_base.h" +#include "window_gui.h" +#include "viewport_func.h" +#include "tilehighlight_func.h" +#include "engine_gui.h" +#include "settings_func.h" +#include "core/geometry_func.hpp" +#include "rail_gui.h" +#include "network/network.h" + +#include "table/sprites.h" +#include "table/strings.h" + +// test creating pool -> creating vehicles +#include "core/pool_func.hpp" + +#include "vehicle_gui_base.h" +#include "vehicle_base.h" +#include "train.h" +#include "vehicle_func.h" + +#include "gfx_type.h" + +#include "engine_func.h" + +// drawing the vehicle length based on occupied tiles +#include "spritecache.h" + +#include "tbtr_template_gui_main.h" +#include "tbtr_template_gui_create.h" +#include "tbtr_template_vehicle.h" + +#include +#include + +#include "safeguards.h" + + +typedef GUIList GUIGroupList; + +enum TemplateReplaceWindowWidgets { + TRW_CAPTION, + + TRW_WIDGET_INSET_GROUPS, + TRW_WIDGET_TOP_MATRIX, + TRW_WIDGET_TOP_SCROLLBAR, + + TRW_WIDGET_INSET_TEMPLATES, + TRW_WIDGET_BOTTOM_MATRIX, + TRW_WIDGET_MIDDLE_SCROLLBAR, + TRW_WIDGET_BOTTOM_SCROLLBAR, + + TRW_WIDGET_TMPL_INFO_INSET, + TRW_WIDGET_TMPL_INFO_PANEL, + + TRW_WIDGET_TMPL_PRE_BUTTON_FLUFF, + + TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REUSE, + TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_KEEP, + TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REFIT, + TRW_WIDGET_TMPL_BUTTONS_CONFIG_RIGHTPANEL, + + TRW_WIDGET_TMPL_BUTTONS_DEFINE, + TRW_WIDGET_TMPL_BUTTONS_EDIT, + TRW_WIDGET_TMPL_BUTTONS_CLONE, + TRW_WIDGET_TMPL_BUTTONS_DELETE, + + TRW_WIDGET_TMPL_BUTTONS_EDIT_RIGHTPANEL, + + TRW_WIDGET_TITLE_INFO_GROUP, + TRW_WIDGET_TITLE_INFO_TEMPLATE, + + TRW_WIDGET_INFO_GROUP, + TRW_WIDGET_INFO_TEMPLATE, + + TRW_WIDGET_TMPL_BUTTONS_SPACER, + + TRW_WIDGET_START, + TRW_WIDGET_TRAIN_FLUFF_LEFT, + TRW_WIDGET_TRAIN_RAILTYPE_DROPDOWN, + TRW_WIDGET_TRAIN_FLUFF_RIGHT, + TRW_WIDGET_STOP, + + TRW_WIDGET_SEL_TMPL_DISPLAY_CREATE, +}; + +static const NWidgetPart _widgets[] = { + // Title bar + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, TRW_CAPTION), SetDataTip(STR_TMPL_RPL_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_SHADEBOX, COLOUR_GREY), + NWidget(WWT_DEFSIZEBOX, COLOUR_GREY), + NWidget(WWT_STICKYBOX, COLOUR_GREY), + EndContainer(), + //Top Matrix + NWidget(NWID_VERTICAL), + NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_INSET_GROUPS), SetMinimalTextLines(1, WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM), SetResize(1, 0), EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, TRW_WIDGET_TOP_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetDataTip(0x1, STR_REPLACE_HELP_LEFT_ARRAY), SetResize(1, 0), SetScrollbar(TRW_WIDGET_TOP_SCROLLBAR), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, TRW_WIDGET_TOP_SCROLLBAR), + EndContainer(), + EndContainer(), + // Template Display + NWidget(NWID_VERTICAL), + NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_INSET_TEMPLATES), SetMinimalTextLines(1, WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM), SetResize(1, 0), EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, TRW_WIDGET_BOTTOM_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetDataTip(0x1, STR_REPLACE_HELP_RIGHT_ARRAY), SetResize(1, 1), SetScrollbar(TRW_WIDGET_MIDDLE_SCROLLBAR), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, TRW_WIDGET_MIDDLE_SCROLLBAR), + EndContainer(), + EndContainer(), + // Info Area + NWidget(NWID_VERTICAL), + NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_TMPL_INFO_INSET), SetMinimalTextLines(1, WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM), SetResize(1,0), EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_TMPL_INFO_PANEL), SetMinimalSize(216,120), SetResize(1,0), SetScrollbar(TRW_WIDGET_BOTTOM_SCROLLBAR), EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, TRW_WIDGET_BOTTOM_SCROLLBAR), + EndContainer(), + EndContainer(), + // Control Area + NWidget(NWID_VERTICAL), + // Spacing + NWidget(WWT_INSET, COLOUR_GREY, TRW_WIDGET_TMPL_PRE_BUTTON_FLUFF), SetMinimalSize(139, 12), SetResize(1,0), EndContainer(), + // Config buttons + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REUSE), SetMinimalSize(150,12), SetResize(0,0), SetDataTip(STR_TMPL_SET_USEDEPOT, STR_TMPL_SET_USEDEPOT_TIP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_KEEP), SetMinimalSize(150,12), SetResize(0,0), SetDataTip(STR_TMPL_SET_KEEPREMAINDERS, STR_TMPL_SET_KEEPREMAINDERS_TIP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REFIT), SetMinimalSize(150,12), SetResize(0,0), SetDataTip(STR_TMPL_SET_REFIT, STR_TMPL_SET_REFIT_TIP), + NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_CONFIG_RIGHTPANEL), SetMinimalSize(12,12), SetResize(1,0), EndContainer(), + EndContainer(), + // Edit buttons + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_DEFINE), SetMinimalSize(75,12), SetResize(0,0), SetDataTip(STR_TMPL_DEFINE_TEMPLATE, STR_TMPL_DEFINE_TEMPLATE), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_EDIT), SetMinimalSize(75,12), SetResize(0,0), SetDataTip(STR_TMPL_EDIT_TEMPLATE, STR_TMPL_EDIT_TEMPLATE), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_CLONE), SetMinimalSize(75,12), SetResize(0,0), SetDataTip(STR_TMPL_CREATE_CLONE_VEH, STR_TMPL_CREATE_CLONE_VEH), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_DELETE), SetMinimalSize(75,12), SetResize(0,0), SetDataTip(STR_TMPL_DELETE_TEMPLATE, STR_TMPL_DELETE_TEMPLATE), + NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_EDIT_RIGHTPANEL), SetMinimalSize(50,12), SetResize(1,0), EndContainer(), + EndContainer(), + EndContainer(), + // Start/Stop buttons + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_START), SetMinimalSize(150, 12), SetDataTip(STR_TMPL_RPL_START, STR_REPLACE_ENGINE_WAGON_SELECT_HELP), + NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_TRAIN_FLUFF_LEFT), SetMinimalSize(15, 12), EndContainer(), + NWidget(WWT_DROPDOWN, COLOUR_GREY, TRW_WIDGET_TRAIN_RAILTYPE_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(0x0, STR_REPLACE_HELP_RAILTYPE), SetResize(1, 0), + NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_TRAIN_FLUFF_RIGHT), SetMinimalSize(16, 12), EndContainer(), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_STOP), SetMinimalSize(150, 12), SetDataTip(STR_TMPL_RPL_STOP, STR_REPLACE_REMOVE_WAGON_HELP), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), +}; + +static WindowDesc _replace_rail_vehicle_desc( + WDP_AUTO, + "template replace window", + 456, 156, + WC_TEMPLATEGUI_MAIN, + WC_NONE, // parent window class + WDF_CONSTRUCTION, + _widgets, lengthof(_widgets) +); + +class TemplateReplaceWindow : public Window { +private: + + GUIGroupList groups; ///< List of groups + byte unitnumber_digits; + + SmallVector indents; ///< Indentation levels + + short line_height; + short matrixContentLeftMargin; + + int details_height; ///< Minimal needed height of the details panels (found so far). + RailType sel_railtype; ///< Type of rail tracks selected. + Scrollbar *vscroll[3]; + // listing/sorting continued + GUITemplateList templates; + GUITemplateList::SortFunction **template_sorter_funcs; + + short selected_template_index; + short selected_group_index; + + bool templateNotice; + bool editInProgress; + +public: + TemplateReplaceWindow(WindowDesc *wdesc, byte dig, int step_h) : Window(wdesc) + { + // listing/sorting + templates.SetSortFuncs(this->template_sorter_funcs); + + // From BaseVehicleListWindow + this->unitnumber_digits = dig; + + this->sel_railtype = RAILTYPE_BEGIN; + this->details_height = 10 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM; + + this->line_height = step_h; + + this->CreateNestedTree(wdesc != NULL); + this->vscroll[0] = this->GetScrollbar(TRW_WIDGET_TOP_SCROLLBAR); + this->vscroll[1] = this->GetScrollbar(TRW_WIDGET_MIDDLE_SCROLLBAR); + this->vscroll[2] = this->GetScrollbar(TRW_WIDGET_BOTTOM_SCROLLBAR); + this->FinishInitNested(VEH_TRAIN); + + this->owner = _local_company; + + this->groups.ForceRebuild(); + this->groups.NeedResort(); + this->BuildGroupList(_local_company); + + this->matrixContentLeftMargin = 40; + this->selected_template_index = -1; + this->selected_group_index = -1; + + this->UpdateButtonState(); + + this->templateNotice = false; + this->editInProgress = false; + + this->templates.ForceRebuild(); + + BuildTemplateGuiList(&this->templates, this->vscroll[1], this->owner, this->sel_railtype); + } + + ~TemplateReplaceWindow() { + DeleteWindowById(WC_CREATE_TEMPLATE, this->window_number); + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + switch (widget) { + case TRW_WIDGET_TOP_MATRIX: + resize->height = GetVehicleListHeight(VEH_TRAIN, FONT_HEIGHT_NORMAL + WD_MATRIX_TOP) / 2; + size->height = 8 * resize->height; + break; + case TRW_WIDGET_BOTTOM_MATRIX: + resize->height = GetVehicleListHeight(VEH_TRAIN, FONT_HEIGHT_NORMAL + WD_MATRIX_TOP); + size->height = 4 * resize->height; + break; + case TRW_WIDGET_TRAIN_RAILTYPE_DROPDOWN: { + Dimension d = {0, 0}; + for (RailType rt = RAILTYPE_BEGIN; rt != RAILTYPE_END; rt++) { + const RailtypeInfo *rti = GetRailTypeInfo(rt); + // Skip rail type if it has no label + if (rti->label == 0) continue; + d = maxdim(d, GetStringBoundingBox(rti->strings.replace_text)); + } + d.width += padding.width; + d.height += padding.height; + *size = maxdim(*size, d); + break; + } + } + } + + virtual void SetStringParameters(int widget) const + { + switch (widget) { + case TRW_CAPTION: + SetDParam(0, STR_TMPL_RPL_TITLE); + break; + } + } + + virtual void DrawWidget(const Rect &r, int widget) const + { + switch (widget) { + case TRW_WIDGET_TOP_MATRIX: { + DrawAllGroupsFunction(this->line_height, r); + break; + } + case TRW_WIDGET_BOTTOM_MATRIX: { + DrawTemplateList(this->line_height, r); + break; + } + case TRW_WIDGET_TMPL_INFO_PANEL: { + DrawTemplateInfo(this->line_height, r); + break; + } + case TRW_WIDGET_INSET_GROUPS: { + DrawString(r.left + 2, r.right - 2, r.top + 2, STR_TMPL_MAINGUI_DEFINEDGROUPS); + break; + } + case TRW_WIDGET_INSET_TEMPLATES: { + DrawString(r.left + 2, r.right - 2, r.top + 2, STR_TMPL_AVAILABLE_TEMPLATES); + break; + } + case TRW_WIDGET_TMPL_INFO_INSET: { + DrawString(r.left + 2, r.right - 2, r.top + 2, STR_TMPL_TEMPLATE_INFO); + break; + } + } + } + + virtual void OnPaint() + { + BuildTemplateGuiList(&this->templates, this->vscroll[1], this->owner, this->sel_railtype); + + this->BuildGroupList(_local_company); + + if (templateNotice) { + BuildTemplateGuiList(&this->templates, vscroll[1], _local_company, this->sel_railtype); + templateNotice = false; + this->SetDirty(); + } + /* sets the colour of that art thing */ + this->GetWidget(TRW_WIDGET_TRAIN_FLUFF_LEFT)->colour = _company_colours[_local_company]; + this->GetWidget(TRW_WIDGET_TRAIN_FLUFF_RIGHT)->colour = _company_colours[_local_company]; + + /* Show the selected railtype in the pulldown menu */ + this->GetWidget(TRW_WIDGET_TRAIN_RAILTYPE_DROPDOWN)->widget_data = GetRailTypeInfo(sel_railtype)->strings.replace_text; + + if ((this->selected_template_index < 0) || (this->selected_template_index >= (short)this->templates.Length())) { + this->vscroll[2]->SetCount(24); + } else { + const TemplateVehicle *tmp = this->templates[this->selected_template_index]; + uint min_height = 30; + uint height = 30; + CargoArray cargo_caps; + short count_columns = 0; + short max_columns = 2; + + for (; tmp != NULL; tmp = tmp->Next()) { + cargo_caps[tmp->cargo_type] += tmp->cargo_cap; + } + + for (CargoID i = 0; i < NUM_CARGO; ++i) { + if (cargo_caps[i] > 0) { + if (count_columns % max_columns == 0) { + height += this->line_height / 3; + } + + ++count_columns; + } + } + + min_height = max(min_height, height); + this->vscroll[2]->SetCount(min_height); + } + + this->DrawWidgets(); + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + if (this->editInProgress) return; + + switch (widget) { + case TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REUSE: { + if ((this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length())) { + uint32 template_index = ((this->templates)[selected_template_index])->index; + + DoCommandP(0, template_index, 0, CMD_TOGGLE_REUSE_DEPOT_VEHICLES, NULL); + } + break; + } + case TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_KEEP: { + if ((this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length())) { + uint32 template_index = ((this->templates)[selected_template_index])->index; + + DoCommandP(0, template_index, 0, CMD_TOGGLE_KEEP_REMAINING_VEHICLES, NULL); + } + break; + } + case TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REFIT: { + if ((this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length())) { + uint32 template_index = ((this->templates)[selected_template_index])->index; + + DoCommandP(0, template_index, 0, CMD_TOGGLE_REFIT_AS_TEMPLATE, NULL); + } + break; + } + case TRW_WIDGET_TMPL_BUTTONS_DEFINE: { + ShowTemplateCreateWindow(0, &templateNotice, &editInProgress, this->line_height); + break; + } + case TRW_WIDGET_TMPL_BUTTONS_EDIT: { + if ((this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length())) { + editInProgress = true; + TemplateVehicle *sel = TemplateVehicle::Get(((this->templates)[selected_template_index])->index); + ShowTemplateCreateWindow(sel, &templateNotice, &editInProgress, this->line_height); + } + break; + } + case TRW_WIDGET_TMPL_BUTTONS_CLONE: { + this->SetWidgetDirty(TRW_WIDGET_TMPL_BUTTONS_CLONE); + this->ToggleWidgetLoweredState(TRW_WIDGET_TMPL_BUTTONS_CLONE); + + if (this->IsWidgetLowered(TRW_WIDGET_TMPL_BUTTONS_CLONE)) { + static const CursorID clone_icon = SPR_CURSOR_CLONE_TRAIN; + SetObjectToPlaceWnd(clone_icon, PAL_NONE, HT_VEHICLE, this); + } else { + ResetObjectToPlace(); + } + break; + } + case TRW_WIDGET_TMPL_BUTTONS_DELETE: + if ((this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length()) && !editInProgress) { + + uint32 template_index = ((this->templates)[selected_template_index])->index; + + bool succeeded = DoCommandP(0, template_index, 0, CMD_DELETE_TEMPLATE_VEHICLE, NULL); + + if (succeeded) { + BuildTemplateGuiList(&this->templates, this->vscroll[1], this->owner, this->sel_railtype); + selected_template_index = -1; + } + } + break; + case TRW_WIDGET_TRAIN_RAILTYPE_DROPDOWN: // Railtype selection dropdown menu + ShowDropDownList(this, GetRailTypeDropDownList(true), sel_railtype, TRW_WIDGET_TRAIN_RAILTYPE_DROPDOWN); + break; + case TRW_WIDGET_TOP_MATRIX: { + uint16 newindex = (uint16)((pt.y - this->nested_array[TRW_WIDGET_TOP_MATRIX]->pos_y) / (this->line_height / 2) ) + this->vscroll[0]->GetPosition(); + if (newindex == this->selected_group_index || newindex >= this->groups.Length()) { + this->selected_group_index = -1; + } else if (newindex < this->groups.Length()) { + this->selected_group_index = newindex; + } + this->UpdateButtonState(); + break; + } + case TRW_WIDGET_BOTTOM_MATRIX: { + uint16 newindex = (uint16)((pt.y - this->nested_array[TRW_WIDGET_BOTTOM_MATRIX]->pos_y) / this->line_height) + this->vscroll[1]->GetPosition(); + if (newindex == this->selected_template_index || newindex >= templates.Length()) { + this->selected_template_index = -1; + } else if (newindex < templates.Length()) { + this->selected_template_index = newindex; + } + this->UpdateButtonState(); + break; + } + case TRW_WIDGET_START: { + if ((this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length()) && + (this->selected_group_index >= 0) && (this->selected_group_index < (short)this->groups.Length())) { + uint32 tv_index = ((this->templates)[selected_template_index])->index; + int current_group_index = (this->groups)[this->selected_group_index]->index; + + DoCommandP(0, current_group_index, tv_index, CMD_ISSUE_TEMPLATE_REPLACEMENT, NULL); + this->UpdateButtonState(); + } + break; + } + case TRW_WIDGET_STOP: + if ((this->selected_group_index < 0) || (this->selected_group_index >= (short)this->groups.Length())) { + return; + } + + int current_group_index = (this->groups)[this->selected_group_index]->index; + + DoCommandP(0, current_group_index, 0, CMD_DELETE_TEMPLATE_REPLACEMENT, NULL); + this->UpdateButtonState(); + break; + } + this->SetDirty(); + } + + virtual bool OnVehicleSelect(const Vehicle *v) + { + bool succeeded = DoCommandP(0, v->index, 0, CMD_CLONE_TEMPLATE_VEHICLE_FROM_TRAIN, NULL); + + if (!succeeded) return false; + + BuildTemplateGuiList(&this->templates, vscroll[1], _local_company, this->sel_railtype); + this->ToggleWidgetLoweredState(TRW_WIDGET_TMPL_BUTTONS_CLONE); + ResetObjectToPlace(); + this->SetDirty(); + + return true; + } + + virtual void OnDropdownSelect(int widget, int index) + { + RailType temp = (RailType) index; + if (temp == this->sel_railtype) return; // we didn't select a new one. No need to change anything + this->sel_railtype = temp; + /* Reset scrollbar positions */ + this->vscroll[0]->SetPosition(0); + this->vscroll[1]->SetPosition(0); + BuildTemplateGuiList(&this->templates, this->vscroll[1], this->owner, this->sel_railtype); + this->SetDirty(); + } + + virtual void OnResize() + { + /* Top Matrix */ + NWidgetCore *nwi = this->GetWidget(TRW_WIDGET_TOP_MATRIX); + this->vscroll[0]->SetCapacityFromWidget(this, TRW_WIDGET_TOP_MATRIX); + nwi->widget_data = (this->vscroll[0]->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START); + /* Bottom Matrix */ + NWidgetCore *nwi2 = this->GetWidget(TRW_WIDGET_BOTTOM_MATRIX); + this->vscroll[1]->SetCapacityFromWidget(this, TRW_WIDGET_BOTTOM_MATRIX); + nwi2->widget_data = (this->vscroll[1]->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START); + /* Info panel */ + NWidgetCore *nwi3 = this->GetWidget(TRW_WIDGET_TMPL_INFO_PANEL); + this->vscroll[2]->SetCapacity(nwi3->current_y); + } + + virtual void OnTick() + { + if (templateNotice) { + BuildTemplateGuiList(&this->templates, this->vscroll[1], this->owner, this->sel_railtype); + this->SetDirty(); + templateNotice = false; + } + + } + + virtual void OnInvalidateData(int data = 0, bool gui_scope = true) + { + this->groups.ForceRebuild(); + this->templates.ForceRebuild(); + this->UpdateButtonState(); + } + + /** For a given group (id) find the template that is issued for template replacement for this group and return this template's index + * from the gui list */ + short FindTemplateIndexForGroup(short gid) const + { + TemplateReplacement *tr = GetTemplateReplacementByGroupID(gid); + if (!tr) return -1; + + for (uint32 i = 0; i < this->templates.Length(); ++i) { + if (templates[i]->index == tr->sel_template) { + return i; + } + } + return -1; + } + + void AddParents(GUIGroupList *source, GroupID parent, int indent) + { + for (const Group **g = source->Begin(); g != source->End(); g++) { + if ((*g)->parent == parent) { + *this->groups.Append() = *g; + *this->indents.Append() = indent; + AddParents(source, (*g)->index, indent + 1); + } + } + } + + /** Sort the groups by their name */ + static int CDECL GroupNameSorter(const Group * const *a, const Group * const *b) + { + static const Group *last_group[2] = { NULL, NULL }; + static char last_name[2][64] = { "", "" }; + + if (*a != last_group[0]) { + last_group[0] = *a; + SetDParam(0, (*a)->index); + GetString(last_name[0], STR_GROUP_NAME, lastof(last_name[0])); + } + + if (*b != last_group[1]) { + last_group[1] = *b; + SetDParam(0, (*b)->index); + GetString(last_name[1], STR_GROUP_NAME, lastof(last_name[1])); + } + + int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting). + if (r == 0) return (*a)->index - (*b)->index; + return r; + } + + void BuildGroupList(Owner owner) + { + if (!this->groups.NeedRebuild()) return; + + this->groups.Clear(); + this->indents.Clear(); + + GUIGroupList list; + + const Group *g; + FOR_ALL_GROUPS(g) { + if (g->owner == owner && g->vehicle_type == VEH_TRAIN) { + *list.Append() = g; + } + } + + list.ForceResort(); + list.Sort(&GroupNameSorter); + + AddParents(&list, INVALID_GROUP, 0); + + this->groups.Compact(); + this->groups.RebuildDone(); + this->vscroll[0]->SetCount(groups.Length()); + } + + void DrawAllGroupsFunction(int line_height, const Rect &r) const + { + int left = r.left + WD_MATRIX_LEFT; + int right = r.right - WD_MATRIX_RIGHT; + int y = r.top; + int max = min(this->vscroll[0]->GetPosition() + this->vscroll[0]->GetCapacity(), this->groups.Length()); + + /* Then treat all groups defined by/for the current company */ + for (int i = this->vscroll[0]->GetPosition(); i < max; ++i) { + const Group *g = (this->groups)[i]; + short g_id = g->index; + + /* Fill the background of the current cell in a darker tone for the currently selected template */ + if (this->selected_group_index == i) { + GfxFillRect(left, y, right, y+(this->line_height) / 2, _colour_gradient[COLOUR_GREY][3]); + } + + SetDParam(0, g_id); + StringID str = STR_GROUP_NAME; + DrawString(left + 30 + this->indents[i] * 10, right, y + 2, str, TC_BLACK); + + /* Draw the template in use for this group, if there is one */ + short template_in_use = FindTemplateIndexForGroup(g_id); + if (template_in_use >= 0) { + SetDParam(0, template_in_use); + DrawString (left, right, y + 2, STR_TMPL_GROUP_USES_TEMPLATE, TC_BLACK, SA_HOR_CENTER); + } else if (GetTemplateReplacementByGroupID(g_id)) { /* If there isn't a template applied from the current group, check if there is one for another rail type */ + DrawString (left, right, y + 2, STR_TMPL_TMPLRPL_EX_DIFF_RAILTYPE, TC_SILVER, SA_HOR_CENTER); + } + + /* Draw the number of trains that still need to be treated by the currently selected template replacement */ + TemplateReplacement *tr = GetTemplateReplacementByGroupID(g_id); + if (tr) { + TemplateVehicle *tv = TemplateVehicle::Get(tr->sel_template); + int num_trains = NumTrainsNeedTemplateReplacement(g_id, tv); + // Draw text + TextColour color = TC_GREY; + if (num_trains) color = TC_BLACK; + DrawString(left, right - 16, y + 2, STR_TMPL_NUM_TRAINS_NEED_RPL, color, SA_RIGHT); + // Draw number + if (num_trains ) { + color = TC_ORANGE; + } else { + color = TC_GREY; + } + SetDParam(0, num_trains); + DrawString(left, right - 4, y + 2, STR_JUST_INT, color, SA_RIGHT); + } + + y += line_height / 2; + } + } + + void DrawTemplateList(int line_height, const Rect &r) const + { + int left = r.left; + int right = r.right; + int y = r.top; + + Scrollbar *draw_vscroll = vscroll[1]; + uint max = min(draw_vscroll->GetPosition() + draw_vscroll->GetCapacity(), this->templates.Length()); + + const TemplateVehicle *v; + for (uint i = draw_vscroll->GetPosition(); i < max; ++i) { + v = (this->templates)[i]; + + /* Fill the background of the current cell in a darker tone for the currently selected template */ + if (this->selected_template_index == (int32) i) { + GfxFillRect(left, y, right, y + this->line_height, _colour_gradient[COLOUR_GREY][3]); + } + + /* Draw a notification string for chains that are not runnable */ + if (v->IsFreeWagonChain()) { + DrawString(left, right - 2, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 2, STR_TMPL_WARNING_FREE_WAGON, TC_RED, SA_RIGHT); + } + + /* Draw the template's length in tile-units */ + SetDParam(0, v->GetRealLength()); + SetDParam(1, 1); + DrawString(left, right - 4, y + 2, STR_TINY_BLACK_DECIMAL, TC_BLACK, SA_RIGHT); + + /* Draw the template */ + DrawTemplate(v, left + 50, right, y); + + /* Buying cost */ + SetDParam(0, CalculateOverallTemplateCost(v)); + DrawString(left + 35, right, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 2, STR_TMPL_TEMPLATE_OVR_VALUE_notinyfont, TC_BLUE, SA_LEFT); + + /* Index of current template vehicle in the list of all templates for its company */ + SetDParam(0, i); + DrawString(left + 5, left + 25, y + 2, STR_BLACK_INT, TC_BLACK, SA_RIGHT); + + /* Draw whether the current template is in use by any group */ + if (v->NumGroupsUsingTemplate() > 0) { + DrawString(left + 35, right, y + line_height - FONT_HEIGHT_SMALL * 2 - 4 - WD_FRAMERECT_BOTTOM - 2, STR_TMP_TEMPLATE_IN_USE, TC_GREEN, SA_LEFT); + } + + /* Draw information about template configuration settings */ + TextColour color; + + color = v->IsSetReuseDepotVehicles() ? TC_LIGHT_BLUE : TC_GREY; + DrawString(right - 225, right, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 2, STR_TMPL_CONFIG_USEDEPOT, color, SA_LEFT); + + color = v->IsSetKeepRemainingVehicles() ? TC_LIGHT_BLUE : TC_GREY; + DrawString(right - 150, right, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 2, STR_TMPL_CONFIG_KEEPREMAINDERS, color, SA_LEFT); + + color = v->IsSetRefitAsTemplate() ? TC_LIGHT_BLUE : TC_GREY; + DrawString(right - 75, right, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 2, STR_TMPL_CONFIG_REFIT, color, SA_LEFT); + + y += line_height; + } + } + + void DrawTemplateInfo(int line_height, const Rect &r) const + { + if ((this->selected_template_index < 0) || (this->selected_template_index >= (short)this->templates.Length())) { + return; + } + + DrawPixelInfo tmp_dpi, *old_dpi; + + if (!FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.right - r.left, r.bottom - r.top)) { + return; + } + + old_dpi = _cur_dpi; + _cur_dpi = &tmp_dpi; + + const TemplateVehicle *tmp = this->templates[this->selected_template_index]; + + /* Draw vehicle performance info */ + SetDParam(2, tmp->max_speed); + SetDParam(1, tmp->power); + SetDParam(0, tmp->weight); + SetDParam(3, tmp->max_te); + DrawString(8, r.right, 4 - this->vscroll[2]->GetPosition(), STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE); + + /* Draw cargo summary */ + short top = 30 - this->vscroll[2]->GetPosition(); + short left = 8; + short count_columns = 0; + short max_columns = 2; + + CargoArray cargo_caps; + for (; tmp != NULL; tmp = tmp->Next()) { + cargo_caps[tmp->cargo_type] += tmp->cargo_cap; + } + int x = left; + for (CargoID i = 0; i < NUM_CARGO; ++i) { + if (cargo_caps[i] > 0) { + count_columns++; + SetDParam(0, i); + SetDParam(1, cargo_caps[i]); + SetDParam(2, _settings_game.vehicle.freight_trains); + DrawString(x, r.right, top, FreightWagonMult(i) > 1 ? STR_TMPL_CARGO_SUMMARY_MULTI : STR_TMPL_CARGO_SUMMARY, TC_LIGHT_BLUE, SA_LEFT); + x += 250; + if (count_columns % max_columns == 0) { + x = left; + top += this->line_height / 3; + } + } + } + + _cur_dpi = old_dpi; + } + + void UpdateButtonState() + { + bool selected_ok = (this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length()); + bool group_ok = (this->selected_group_index >= 0) && (this->selected_group_index < (short)this->groups.Length()); + + short g_id = -1; + if (group_ok) { + const Group *g = (this->groups)[this->selected_group_index]; + g_id = g->index; + } + + this->SetWidgetDisabledState(TRW_WIDGET_TMPL_BUTTONS_EDIT, !selected_ok); + this->SetWidgetDisabledState(TRW_WIDGET_TMPL_BUTTONS_DELETE, !selected_ok); + this->SetWidgetDisabledState(TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REUSE, !selected_ok); + this->SetWidgetDisabledState(TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_KEEP, !selected_ok); + this->SetWidgetDisabledState(TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REFIT, !selected_ok); + + this->SetWidgetDisabledState(TRW_WIDGET_START, !(selected_ok && group_ok && FindTemplateIndexForGroup(g_id) != this->selected_template_index)); + this->SetWidgetDisabledState(TRW_WIDGET_STOP, !(group_ok && GetTemplateReplacementByGroupID(g_id) != NULL)); + } +}; + +void ShowTemplateReplaceWindow(byte dig, int step_h) +{ + new TemplateReplaceWindow(&_replace_rail_vehicle_desc, dig, step_h); +} diff --git a/src/tbtr_template_gui_main.h b/src/tbtr_template_gui_main.h new file mode 100644 index 0000000000..0a42c6e8c4 --- /dev/null +++ b/src/tbtr_template_gui_main.h @@ -0,0 +1,28 @@ +/* $Id$ */ + +/* + * 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_gui_main.h Template-based train replacement: main GUI header. */ + +#ifndef TEMPLATE_GUI_H +#define TEMPLATE_GUI_H + +#include "engine_type.h" +#include "group_type.h" +#include "vehicle_type.h" +#include "string_func.h" +#include "strings_func.h" + +#include "tbtr_template_vehicle.h" +#include "tbtr_template_vehicle_func.h" + +typedef GUIList GUIGroupList; + +void ShowTemplateReplaceWindow(byte, int); + +#endif diff --git a/src/tbtr_template_vehicle.cpp b/src/tbtr_template_vehicle.cpp new file mode 100644 index 0000000000..1a87266f0d --- /dev/null +++ b/src/tbtr_template_vehicle.cpp @@ -0,0 +1,214 @@ +/* $Id$ */ + +/* + * 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.cpp Template-based train replacement: template vehicle. */ + +#include "stdafx.h" +#include "company_func.h" +#include "train.h" +#include "command_func.h" +#include "engine_func.h" +#include "vehicle_func.h" +#include "autoreplace_func.h" +#include "autoreplace_gui.h" +#include "group.h" +#include "articulated_vehicles.h" +#include "core/random_func.hpp" +#include "core/pool_type.hpp" +#include "engine_type.h" +#include "group_type.h" +#include "core/pool_func.hpp" + +#include "table/strings.h" + +#include "newgrf.h" + +#include "vehicle_type.h" +#include "vehicle_base.h" +#include "vehicle_func.h" + +#include "table/train_cmd.h" + +#include "tbtr_template_vehicle.h" + +// since doing stuff with sprites +#include "newgrf_spritegroup.h" +#include "newgrf_engine.h" +#include "newgrf_cargo.h" + +#include "safeguards.h" + +TemplatePool _template_pool("TemplatePool"); +INSTANTIATE_POOL_METHODS(Template) + +TemplateReplacementPool _template_replacement_pool("TemplateReplacementPool"); +INSTANTIATE_POOL_METHODS(TemplateReplacement) + + +TemplateVehicle::TemplateVehicle(VehicleType ty, EngineID eid, byte subtypeflag, Owner current_owner) +{ + this->type = ty; + this->engine_type = eid; + + this->reuse_depot_vehicles = true; + this->keep_remaining_vehicles = true; + + this->first = this; + this->next = 0x0; + this->previous = 0x0; + this->owner_b = _current_company; + + this->cur_image = SPR_IMG_QUERY; + + this->owner = current_owner; + + this->real_consist_length = 0; +} + +TemplateVehicle::~TemplateVehicle() { + TemplateVehicle *v = this->Next(); + this->SetNext(NULL); + + delete v; +} + +/** getting */ +void TemplateVehicle::SetNext(TemplateVehicle *v) { this->next = v; } +void TemplateVehicle::SetPrev(TemplateVehicle *v) { this->previous = v; } +void TemplateVehicle::SetFirst(TemplateVehicle *v) { this->first = v; } + +TemplateVehicle* TemplateVehicle::GetNextUnit() const +{ + TemplateVehicle *tv = this->Next(); + while (tv && HasBit(tv->subtype, GVSF_ARTICULATED_PART)) { + tv = tv->Next(); + } + if (tv && HasBit(tv->subtype, GVSF_MULTIHEADED) && !HasBit(tv->subtype, GVSF_ENGINE)) tv = tv->Next(); + return tv; +} + +TemplateVehicle* TemplateVehicle::GetPrevUnit() +{ + TemplateVehicle *tv = this->Prev(); + while (tv && HasBit(tv->subtype, GVSF_ARTICULATED_PART|GVSF_ENGINE)) { + tv = tv->Prev(); + } + if (tv && HasBit(tv->subtype, GVSF_MULTIHEADED|GVSF_ENGINE)) tv = tv->Prev(); + return tv; +} + +/** setting */ +void appendTemplateVehicle(TemplateVehicle *orig, TemplateVehicle *newv) +{ + if (!orig) return; + while (orig->Next()) orig = orig->Next(); + orig->SetNext(newv); + newv->SetPrev(orig); + newv->SetFirst(orig->First()); +} + +void insertTemplateVehicle(TemplateVehicle *orig, TemplateVehicle *newv, TemplateVehicle *insert_after) +{ + if (!orig || !insert_after) return; + TemplateVehicle *insert_before = insert_after->Next(); + insert_after->SetNext(newv); + insert_before->SetPrev(newv); + newv->SetPrev(insert_after); + newv->SetNext(insert_before); + newv->SetFirst(insert_after); +} + +/** Length() + * @return: length of vehicle, including current part + */ +int TemplateVehicle::Length() const +{ + int l = 1; + const TemplateVehicle *tmp = this; + while (tmp->Next()) { + tmp = tmp->Next(); + l++; + } + return l; +} + +TemplateReplacement* GetTemplateReplacementByGroupID(GroupID gid) +{ + TemplateReplacement *tr; + FOR_ALL_TEMPLATE_REPLACEMENTS(tr) { + if (tr->Group() == gid) { + return tr; + } + } + return NULL; +} + +TemplateReplacement* GetTemplateReplacementByTemplateID(TemplateID tid) +{ + TemplateReplacement *tr; + FOR_ALL_TEMPLATE_REPLACEMENTS(tr) { + if (tr->Template() == tid) { + return tr; + } + } + return NULL; +} + +bool IssueTemplateReplacement(GroupID gid, TemplateID tid) +{ + TemplateReplacement *tr = GetTemplateReplacementByGroupID(gid); + + if (tr) { + /* Then set the new TemplateVehicle and return */ + tr->SetTemplate(tid); + return true; + } else if (TemplateReplacement::CanAllocateItem()) { + tr = new TemplateReplacement(gid, tid); + return true; + } + + else return false; +} + +short TemplateVehicle::NumGroupsUsingTemplate() const +{ + short amount = 0; + const TemplateReplacement *tr; + FOR_ALL_TEMPLATE_REPLACEMENTS(tr) { + if (tr->sel_template == this->index) { + amount++; + } + } + return amount; +} + +short TemplateVehicle::CountEnginesInChain() +{ + TemplateVehicle *tv = this->first; + short count = 0; + for (; tv != NULL; tv = tv->GetNextUnit()) { + if (HasBit(tv->subtype, GVSF_ENGINE)) { + count++; + } + } + return count; +} + +short deleteIllegalTemplateReplacements(GroupID g_id) +{ + short del_amount = 0; + const TemplateReplacement *tr; + FOR_ALL_TEMPLATE_REPLACEMENTS(tr) { + if (tr->group == g_id) { + delete tr; + del_amount++; + } + } + return del_amount; +} diff --git a/src/tbtr_template_vehicle.h b/src/tbtr_template_vehicle.h new file mode 100644 index 0000000000..0631f85b8d --- /dev/null +++ b/src/tbtr_template_vehicle.h @@ -0,0 +1,208 @@ +/* $Id$ */ + +/* + * 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.h Template-based train replacement: template vehicle header. */ + +#ifndef TEMPLATE_VEH_H +#define TEMPLATE_VEH_H + +#include "company_func.h" + +#include "vehicle_type.h" +#include "vehicle_base.h" +#include "vehicle_func.h" + +#include "articulated_vehicles.h" +#include "newgrf_callbacks.h" +#include "newgrf_engine.h" +#include "newgrf_spritegroup.h" + +#include "engine_base.h" +#include "engine_type.h" +#include "engine_func.h" + +#include "sortlist_type.h" + +#define FOR_ALL_TEMPLATES_FROM(var, start) FOR_ALL_ITEMS_FROM(TemplateVehicle, template_index, var, start) +#define FOR_ALL_TEMPLATES(var) FOR_ALL_TEMPLATES_FROM(var, 0) + +#define FOR_ALL_TEMPLATE_REPLACEMENTS_FROM(var, start) FOR_ALL_ITEMS_FROM(TemplateReplacement, template_replacement_index, var, start) +#define FOR_ALL_TEMPLATE_REPLACEMENTS(var) FOR_ALL_TEMPLATE_REPLACEMENTS_FROM(var, 0) + +struct TemplateVehicle; +struct TemplateReplacement; + +typedef uint16 TemplateID; + + +static const uint16 CONSIST_HEAD = 0x0; +static const uint16 CONSIST_TAIL = 0xffff; + +/** A pool allowing to store up to ~64k templates */ +typedef Pool TemplatePool; +extern TemplatePool _template_pool; + +/// listing/sorting templates +typedef GUIList GUITemplateList; + +struct TemplateVehicle : TemplatePool::PoolItem<&_template_pool>, BaseVehicle { +private: + TemplateVehicle *next; ///< pointer to the next vehicle in the chain + TemplateVehicle *previous; ///< NOSAVE: pointer to the previous vehicle in the chain + TemplateVehicle *first; ///< NOSAVE: pointer to the first vehicle in the chain + +public: + friend const SaveLoad* GTD(); + friend void AfterLoadTemplateVehicles(); + + // Template usage configuration + bool reuse_depot_vehicles; + bool keep_remaining_vehicles; + bool refit_as_template; + + // Things derived from a virtual train + TemplateVehicle *other_multiheaded_part; ///< Multiheaded Engine support + Money value; ///< Value of the vehicle + Owner owner; + OwnerByte owner_b; + + EngineID engine_type; ///< The type of engine used for this vehicle. + CargoID cargo_type; ///< type of cargo this vehicle is carrying + uint16 cargo_cap; ///< total capacity + byte cargo_subtype; + + byte subtype; + RailTypeByte railtype; + + VehicleID index; + + uint16 real_consist_length; + + uint16 max_speed; + uint32 power; + uint32 weight; + uint32 max_te; + + byte spritenum; + SpriteID cur_image; + uint32 image_width; + const SpriteGroup *sgroup; + + TemplateVehicle(VehicleType type = VEH_INVALID, EngineID e = INVALID_ENGINE, byte B = 0, Owner = _local_company); + TemplateVehicle(EngineID, RailVehicleInfo*); + + TemplateVehicle(EngineID eid) + { + next = NULL; + previous = NULL; + first = this; + engine_type = eid; + this->reuse_depot_vehicles = true; + this->keep_remaining_vehicles = true; + this->refit_as_template = true; + } + + ~TemplateVehicle(); + + inline TemplateVehicle* Next() const { return this->next; } + inline TemplateVehicle* Prev() const { return this->previous; } + inline TemplateVehicle* First() const { return this->first; } + + void SetNext(TemplateVehicle*); + void SetPrev(TemplateVehicle*); + void SetFirst(TemplateVehicle*); + + TemplateVehicle* GetNextUnit() const; + TemplateVehicle* GetPrevUnit(); + + bool IsSetReuseDepotVehicles() const { return this->reuse_depot_vehicles; } + bool IsSetKeepRemainingVehicles() const { return this->keep_remaining_vehicles; } + bool IsSetRefitAsTemplate() const { return this->refit_as_template; } + void ToggleReuseDepotVehicles() { this->reuse_depot_vehicles = !this->reuse_depot_vehicles; } + void ToggleKeepRemainingVehicles() { this->keep_remaining_vehicles = !this->keep_remaining_vehicles; } + void ToggleRefitAsTemplate() { this->refit_as_template = !this->refit_as_template; } + + bool IsPrimaryVehicle() const { return this->IsFrontEngine(); } + inline bool IsFrontEngine() const { return HasBit(this->subtype, GVSF_FRONT); } + inline bool HasArticulatedPart() const { return this->Next() != NULL && this->Next()->IsArticulatedPart(); } + + inline bool IsArticulatedPart() const { return HasBit(this->subtype, GVSF_ARTICULATED_PART); } + inline bool IsMultiheaded() const { return HasBit(this->subtype, GVSF_MULTIHEADED); } + + inline bool IsFreeWagonChain() const { return HasBit(this->subtype, GVSF_FREE_WAGON); } + + // since CmdBuildTemplateVehicle(...) + inline void SetFrontEngine() { SetBit(this->subtype, GVSF_FRONT); } + inline void SetEngine() { SetBit(this->subtype, GVSF_ENGINE); } + inline void SetArticulatedPart() { SetBit(this->subtype, GVSF_ARTICULATED_PART); } + inline void SetMultiheaded() { SetBit(this->subtype, GVSF_MULTIHEADED); } + + inline void SetWagon() { SetBit(this->subtype, GVSF_WAGON); } + inline void SetFreeWagon() { SetBit(this->subtype, GVSF_FREE_WAGON); } + + inline uint16 GetRealLength() const { return this->real_consist_length; } + inline void SetRealLength(uint16 len) { this->real_consist_length = len; } + + int Length() const; + + SpriteID GetImage(Direction) const; + SpriteID GetSpriteID() const; + + short NumGroupsUsingTemplate() const; + + short CountEnginesInChain(); + +}; + +void appendTemplateVehicle(TemplateVehicle*, TemplateVehicle*); +void insertTemplateVehicle(TemplateVehicle*, TemplateVehicle*, TemplateVehicle*); + +void NeutralizeVehicleStatus(Train*); +void SplitVehicleRemainders(Train*); + +// TemplateReplacement stuff + +typedef Pool TemplateReplacementPool; +extern TemplateReplacementPool _template_replacement_pool; + +struct TemplateReplacement : TemplateReplacementPool::PoolItem<&_template_replacement_pool> { + GroupID group; + TemplateID sel_template; + + TemplateReplacement(GroupID gid, TemplateID tid) { this->group=gid; this->sel_template=tid; } + TemplateReplacement() {} + ~TemplateReplacement() {} + + inline GroupID Group() { return this->group; } + inline GroupID Template() { return this->sel_template; } + + inline void SetGroup(GroupID gid) { this->group = gid; } + inline void SetTemplate(TemplateID tid) { this->sel_template = tid; } + + inline TemplateID GetTemplateVehicleID() { return sel_template; } + + inline const TemplateVehicle* GetTemplateVehicle() + { + const TemplateVehicle *tv; + FOR_ALL_TEMPLATES(tv) { + if (tv->index == this->sel_template) { + return tv; + } + } + return NULL; + } +}; + +TemplateReplacement* GetTemplateReplacementByGroupID(GroupID); +TemplateReplacement* GetTemplateReplacementByTemplateID(TemplateID); +bool IssueTemplateReplacement(GroupID, TemplateID); + +short deleteIllegalTemplateReplacements(GroupID); + +#endif /* TEMPLATE_VEH_H */ diff --git a/src/tbtr_template_vehicle_func.cpp b/src/tbtr_template_vehicle_func.cpp new file mode 100644 index 0000000000..7064659296 --- /dev/null +++ b/src/tbtr_template_vehicle_func.cpp @@ -0,0 +1,547 @@ +/* $Id$ */ + +/* + * 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 "window_gui.h" +#include "gfx_func.h" +#include "window_func.h" +#include "command_func.h" +#include "vehicle_gui.h" +#include "train.h" +#include "strings_func.h" +#include "vehicle_func.h" +#include "core/geometry_type.hpp" +#include "debug.h" + +#include "table/sprites.h" +#include "table/strings.h" + +#include "cargoaction.h" +#include "train.h" +#include "company_func.h" +#include "newgrf.h" +#include "spritecache.h" +#include "articulated_vehicles.h" +#include "autoreplace_func.h" + +#include "depot_base.h" + +#include "tbtr_template_vehicle.h" +#include "tbtr_template_vehicle_func.h" + +#include +#include + +#include "safeguards.h" + +Vehicle *vhead, *vtmp; +static const uint MAX_ARTICULATED_PARTS = 100; + +#ifdef _DEBUG +// debugging printing functions for convenience, usually called from gdb +void tbtr_debug_pat() +{ + TemplateVehicle *tv; + FOR_ALL_TEMPLATES(tv) { + if (tv->Prev()) continue; + tbtr_debug_ptv(tv); + printf("__________\n"); + } +} + +void tbtr_debug_pav() +{ + Train *t; + FOR_ALL_TRAINS(t) { + if (t->Previous()) continue; + tbtr_debug_pvt(t); + printf("__________\n"); + } +} + +void tbtr_debug_ptv(TemplateVehicle* tv) +{ + if (!tv) return; + while (tv->Next() ) { + printf("eid:%3d st:%2d tv:%p next:%p cargo: %d cargo_sub: %d\n", tv->engine_type, tv->subtype, tv, tv->Next(), tv->cargo_type, tv->cargo_subtype); + tv = tv->Next(); + } + printf("eid:%3d st:%2d tv:%p next:%p cargo: %d cargo_sub: %d\n", tv->engine_type, tv->subtype, tv, tv->Next(), tv->cargo_type, tv->cargo_subtype); +} + +void tbtr_debug_pvt (const Train *printme) +{ + for (const Train *tmp = printme; tmp; tmp = tmp->Next()) { + if (tmp->index <= 0) { + printf("train has weird index: %d %d %p\n", tmp->index, tmp->engine_type, tmp); + return; + } + printf("eid:%3d index:%2d subtype:%2d vehstat: %d cargo_t: %d cargo_sub: %d ref:%p\n", tmp->engine_type, tmp->index, tmp->subtype, tmp->vehstatus, tmp->cargo_type, tmp->cargo_subtype, tmp); + } +} +#endif + +void BuildTemplateGuiList(GUITemplateList *list, Scrollbar *vscroll, Owner oid, RailType railtype) +{ + list->Clear(); + const TemplateVehicle *tv; + + FOR_ALL_TEMPLATES(tv) { + if (tv->owner == oid && (tv->IsPrimaryVehicle() || tv->IsFreeWagonChain()) && TemplateVehicleContainsEngineOfRailtype(tv, railtype)) { + *list->Append() = tv; + } + } + + list->RebuildDone(); + if (vscroll) vscroll->SetCount(list->Length()); +} + +Money CalculateOverallTemplateCost(const TemplateVehicle *tv) +{ + Money val = 0; + + for (; tv; tv = tv->Next()) { + val += (Engine::Get(tv->engine_type))->GetCost(); + } + return val; +} + +void DrawTemplate(const TemplateVehicle *tv, int left, int right, int y) +{ + if (!tv) return; + + const TemplateVehicle *t = tv; + int offset = left; + + while (t) { + PaletteID pal = GetEnginePalette(t->engine_type, _current_company); + DrawSprite(t->cur_image, pal, offset, y + 12); + + offset += t->image_width; + t = t->Next(); + } +} + +// copy important stuff from the virtual vehicle to the template +inline 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; + tmp->value = virt->value; + + // 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; + + const GroundVehicleCache *gcache = virt->GetGroundVehicleCache(); + tmp->max_speed = virt->GetDisplayMaxSpeed(); + tmp->power = gcache->cached_power; + tmp->weight = gcache->cached_weight; + tmp->max_te = gcache->cached_max_te / 1000; + + tmp->spritenum = virt->spritenum; + tmp->cur_image = virt->GetImage(DIR_W, EIT_PURCHASE); + Point *p = new Point(); + tmp->image_width = virt->GetDisplayImageWidth(p); +} + +// create a full TemplateVehicle based train according to a virtual train +TemplateVehicle* TemplateVehicleFromVirtualTrain(Train *virt) +{ + if (!virt) return NULL; + + Train *init_virt = virt; + + int len = CountVehiclesInChain(virt); + if (!TemplateVehicle::CanAllocateItem(len)) { + return NULL; + } + + TemplateVehicle *tmp; + TemplateVehicle *prev = NULL; + 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(); +} + +// return last in a chain (really last, so even a singular articulated part of a vehicle if the last one is artic) +inline TemplateVehicle* Last(TemplateVehicle *chain) +{ + if (!chain) return NULL; + while (chain->Next()) { + chain = chain->Next(); + } + return chain; +} + +inline Train* Last(Train *chain) +{ + if (!chain) return NULL; + while (chain->GetNextUnit()) { + chain = chain->GetNextUnit(); + } + return chain; +} + +// return: pointer to former vehicle +TemplateVehicle *DeleteTemplateVehicle(TemplateVehicle *todel) +{ + if (!todel) return NULL; + TemplateVehicle *cur = todel; + delete todel; + return cur; +} + +// forward declaration, defined in train_cmd.cpp +CommandCost CmdSellRailWagon(DoCommandFlag, Vehicle*, uint16, uint32); + +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 templatereplacement that belongs to the given group +TemplateVehicle* GetTemplateVehicleByGroupID(GroupID gid) { + TemplateReplacement *tr; + // first try to find a templatereplacement issued for the given groupid + FOR_ALL_TEMPLATE_REPLACEMENTS(tr) { + if (tr->Group() == gid) { + return TemplateVehicle::GetIfValid(tr->Template()); // there can be only one + } + } + // if that didn't work, try to find a templatereplacement for ALL_GROUP + if (gid != ALL_GROUP) { + FOR_ALL_TEMPLATE_REPLACEMENTS(tr) { + if (tr->Group() == ALL_GROUP) { + return TemplateVehicle::GetIfValid(tr->Template()); + } + } + } + // if all failed, just return null + return NULL; +} + +/** + * Check a template consist whether it contains any engine of the given railtype + */ +bool TemplateVehicleContainsEngineOfRailtype(const TemplateVehicle *tv, RailType type) +{ + /* 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; +} + +//helper +bool ChainContainsVehicle(Train *chain, Train *mem) +{ + for (; chain; chain = chain->Next()) { + if (chain == mem) { + return true; + } + } + return false; +} + +// has O(n) +Train* ChainContainsEngine(EngineID eid, Train *chain) { + for (; chain; chain=chain->GetNextUnit()) + if (chain->engine_type == eid) + return chain; + return NULL; +} + +// has O(n^2) +Train* DepotContainsEngine(TileIndex tile, EngineID eid, Train *not_in = NULL) +{ + Train *t; + FOR_ALL_TRAINS(t) { + // 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 NULL, no check is needed + if (t->tile == tile + // 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 + && ((t->IsPrimaryVehicle() && t->IsStoppedInDepot()) || t->IsFreeWagon()) + && t->engine_type == eid + && (not_in == NULL || ChainContainsVehicle(not_in, t) == false)) { + return t; + } + } + return NULL; +} + +void CopyStatus(Train *from, Train *to) +{ + DoCommand(to->tile, from->group_id, to->index, DC_EXEC, CMD_ADD_VEHICLE_GROUP); + to->cargo_type = from->cargo_type; + to->cargo_subtype = from->cargo_subtype; + + // swap names + char *tmp = to->name; + to->name = from->name; + from->name = tmp; +} + +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, FreeUnitIDGenerator(VEH_TRAIN, t->owner).NextID(), DC_EXEC, CMD_SET_VEHICLE_UNIT_NUMBER); + DoCommand(0, t->index, 0, DC_EXEC, CMD_RENAME_VEHICLE, NULL); +} + +bool TrainMatchesTemplate(const Train *t, TemplateVehicle *tv) { + while (t && tv) { + if (t->engine_type != tv->engine_type) { + return false; + } + t = t->GetNextUnit(); + tv = tv->GetNextUnit(); + } + if ((t && !tv) || (!t && tv)) { + return false; + } + return true; +} + + +bool TrainMatchesTemplateRefit(const Train *t, TemplateVehicle *tv) +{ + if (!tv->refit_as_template) { + return true; + } + + while (t && tv) { + if (t->cargo_type != tv->cargo_type || t->cargo_subtype != tv->cargo_subtype) { + return false; + } + t = t->GetNextUnit(); + tv = tv->GetNextUnit(); + } + return true; +} + +void BreakUpRemainders(Train *t) +{ + while (t) { + Train *move; + if (HasBit(t->subtype, GVSF_ENGINE)) { + move = t; + t = t->Next(); + DoCommand(move->tile, move->index, INVALID_VEHICLE, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); + NeutralizeStatus(move); + } else { + t = t->Next(); + } + } +} + +short CountEnginesInChain(Train *t) +{ + short count = 0; + for (; t != NULL; t = t->GetNextUnit()) { + if (HasBit(t->subtype, GVSF_ENGINE)) { + count++; + } + } + return count; +} + +int countOccurrencesInTrain(Train *t, EngineID eid) +{ + int count = 0; + Train *tmp = t; + for (; tmp != NULL; tmp = tmp->GetNextUnit()) { + if (tmp->engine_type == eid) { + count++; + } + } + return count; +} + +int countOccurrencesInTemplateVehicle(TemplateVehicle *contain, EngineID eid) +{ + int count = 0; + for (; contain; contain=contain->GetNextUnit()) { + if (contain->engine_type == eid) { + count++; + } + } + return count; +} + +int countOccurrencesInDepot(TileIndex tile, EngineID eid, Train *not_in = NULL) +{ + int count = 0; + Vehicle *v; + FOR_ALL_VEHICLES(v) { + // 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 NULL, no check is needed + if (v->tile == tile && v->IsStoppedInDepot() && v->engine_type == eid && + (not_in == 0 || ChainContainsVehicle(not_in, (Train*)v) == false)) { + count++; + } + } + return count; +} + +// basically does the same steps as CmdTemplateReplaceVehicle but without actually moving things around +CommandCost CalculateTemplateReplacementCost(Train *incoming) +{ + TileIndex tile = incoming->tile; + TemplateVehicle *tv = GetTemplateVehicleByGroupID(incoming->group_id); + CommandCost estimate(EXPENSES_NEW_VEHICLES); + + // count for each different eid in the incoming train + std::map unique_eids; + for (TemplateVehicle *tmp = tv; tmp != NULL; tmp = tmp->GetNextUnit()) { + unique_eids[tmp->engine_type]++; + } + std::map::iterator it = unique_eids.begin(); + for (; it != unique_eids.end(); it++) { + it->second -= countOccurrencesInTrain(incoming, it->first); + it->second -= countOccurrencesInDepot(incoming->tile, it->first, incoming); + if (it->second < 0) it->second = 0; + } + + // get overall buying cost + for (it = unique_eids.begin(); it != unique_eids.end(); it++) { + for (int j = 0; j < it->second; j++) { + estimate.AddCost(DoCommand(tile, it->first, 0, DC_NONE, CMD_BUILD_VEHICLE)); + } + } + + return estimate; +} + +// 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; +} + +int NumTrainsNeedTemplateReplacement(GroupID g_id, TemplateVehicle *tv) +{ + int count = 0; + if (!tv) return count; + + const Train *t; + FOR_ALL_TRAINS(t) { + if (t->IsPrimaryVehicle() && t->group_id == g_id && (!TrainMatchesTemplate(t, tv) || !TrainMatchesTemplateRefit(t, tv))) { + count++; + } + } + return count; +} +// refit each vehicle in t as is in tv, assume t and tv contain the same types of vehicles +void CmdRefitTrainFromTemplate(Train *t, TemplateVehicle *tv, DoCommandFlag flags) +{ + while (t && tv) { + // refit t as tv + uint32 cb = GetCmdRefitVeh(t); + + DoCommand(t->tile, t->index, tv->cargo_type | tv->cargo_subtype << 8 | 1 << 16 | (1 << 5), flags, cb); + + // next + t = t->GetNextUnit(); + tv = tv->GetNextUnit(); + } +} + +/** 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()); + + CargoID _cargo_type = old_veh->cargo_type; + byte _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 != NULL && 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 = 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; + } + } + } + + // TODO: needs to be implemented, too + // // from autoreplace_cmd.cpp : 121 + /* Any left-overs will be thrown away, but not their feeder share. */ + //if (src->cargo_cap < src->cargo.TotalCount()) src->cargo.Truncate(src->cargo.TotalCount() - src->cargo_cap); + + /* Update train weight etc., the old vehicle will be sold anyway */ + new_head->ConsistChanged(CCF_LOADUNLOAD); +} diff --git a/src/tbtr_template_vehicle_func.h b/src/tbtr_template_vehicle_func.h new file mode 100644 index 0000000000..0e2989ec87 --- /dev/null +++ b/src/tbtr_template_vehicle_func.h @@ -0,0 +1,83 @@ +/* $Id$ */ + +/* + * 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.h Template-based train replacement: template vehicle functions headers. */ + +#ifndef TEMPLATE_VEHICLE_FUNC_H +#define TEMPLATE_VEHICLE_FUNC_H + +#include "stdafx.h" +#include "window_gui.h" + +#include "tbtr_template_vehicle.h" + +Train* VirtualTrainFromTemplateVehicle(TemplateVehicle* tv); + +void DrawTemplateVehicle(const TemplateVehicle*, int, int, int, VehicleID, int, VehicleID); + +void BuildTemplateGuiList(GUITemplateList*, Scrollbar*, Owner, RailType); + +Money CalculateOverallTemplateCost(const TemplateVehicle*); + +void DrawTemplateTrain(const TemplateVehicle*, int, int, int); + +SpriteID GetSpriteID(EngineID, bool); + +void DrawTemplate(const TemplateVehicle*, int, int, int); + +int GetTemplateDisplayImageWidth(EngineID); + +TemplateVehicle *CreateNewTemplateVehicle(EngineID); + +void setupVirtTrain(const TemplateVehicle*, Train*); + +TemplateVehicle* TemplateVehicleFromVirtualTrain(Train *virt); + +inline TemplateVehicle* Last(TemplateVehicle*); + +TemplateVehicle *DeleteTemplateVehicle(TemplateVehicle*); + +Train* DeleteVirtualTrainPart(Train*, Train*); +Train* DeleteVirtualTrain(Train*, Train *); + +CommandCost CmdTemplateReplaceVehicle(Train*, bool, DoCommandFlag); + +#ifdef _DEBUG +// for testing +void tbtr_debug_pat(); +void tbtr_debug_pav(); +void tbtr_debug_ptv(TemplateVehicle*); +void tbtr_debug_pvt(const Train*); +#endif + +TemplateVehicle* GetTemplateVehicleByGroupID(GroupID); +bool ChainContainsVehicle(Train*, Train*); +Train* ChainContainsEngine(EngineID, Train*); +Train* DepotContainsEngine(TileIndex, EngineID, Train*); + +int NumTrainsNeedTemplateReplacement(GroupID, TemplateVehicle*); + +CommandCost CalculateTemplateReplacementCost(Train*); +CommandCost TestBuyAllTemplateVehiclesInChain(TemplateVehicle *tv, TileIndex tile); + +void CmdRefitTrainFromTemplate(Train *t, TemplateVehicle *tv, DoCommandFlag); +void BreakUpRemainders(Train *t); + +short CountEnginesInChain(Train*); + +bool TemplateVehicleContainsEngineOfRailtype(const TemplateVehicle*, RailType); + +void TransferCargoForTrain(Train*, Train*); + +void NeutralizeStatus(Train *t); + +bool TrainMatchesTemplate(const Train *t, TemplateVehicle *tv); +bool TrainMatchesTemplateRefit(const Train *t, TemplateVehicle *tv); + +#endif diff --git a/src/train.h b/src/train.h index 280d59ebdd..99b6699dc2 100644 --- a/src/train.h +++ b/src/train.h @@ -123,6 +123,7 @@ struct Train FINAL : public GroundVehicle { bool Tick(); void OnNewDay(); uint Crash(bool flooded = false); + Money CalculateCurrentOverallValue() const; Trackdir GetVehicleTrackdir() const; TileIndex GetOrderStationLocation(StationID station); bool FindClosestDepot(TileIndex *location, DestinationID *destination, bool *reverse); @@ -163,6 +164,17 @@ struct Train FINAL : public GroundVehicle { return v; } + /* Get the last vehicle of a chain + * @return pointer the last vehicle in a chain + */ + inline Train *GetLastUnit() { + Train *tmp = this; + while (tmp->GetNextUnit()) { + tmp = tmp->GetNextUnit(); + } + return tmp; + } + /** * Calculate the offset from this vehicle's center to the following center taking the vehicle lengths into account. * @return Offset from center to center. @@ -336,6 +348,13 @@ protected: // These functions should not be called outside acceleration code. } }; + +CommandCost CmdMoveRailVehicle(TileIndex, DoCommandFlag , uint32, uint32, const char *); +CommandCost CmdMoveVirtualRailVehicle(TileIndex, DoCommandFlag, uint32, uint32, const char*); + +Train* CmdBuildVirtualRailWagon(const Engine*); +Train* CmdBuildVirtualRailVehicle(EngineID); + #define FOR_ALL_TRAINS(var) FOR_ALL_VEHICLES_OF_TYPE(Train, var) #endif /* TRAIN_H */ diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 6f2feeaab7..5f8d9ae1f0 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -35,6 +35,9 @@ #include "order_backup.h" #include "zoom_func.h" #include "newgrf_debug.h" +#include "tbtr_template_vehicle_func.h" +#include "autoreplace_func.h" +#include "engine_func.h" #include "table/strings.h" #include "table/train_cmd.h" @@ -259,7 +262,7 @@ void Train::ConsistChanged(ConsistChangeFlags allowed_changes) if (this->IsFrontEngine()) { this->UpdateAcceleration(); - SetWindowDirty(WC_VEHICLE_DETAILS, this->index); + if (!HasBit(this->subtype, GVSF_VIRTUAL)) SetWindowDirty(WC_VEHICLE_DETAILS, this->index); InvalidateWindowData(WC_VEHICLE_REFIT, this->index, VIWD_CONSIST_CHANGED); InvalidateWindowData(WC_VEHICLE_ORDERS, this->index, VIWD_CONSIST_CHANGED); InvalidateNewGRFInspectWindow(GSF_TRAINS, this->index); @@ -1158,6 +1161,7 @@ static void NormaliseTrainHead(Train *head) * @param p1 various bitstuffed elements * - p1 (bit 0 - 19) source vehicle index * - p1 (bit 20) move all vehicles following the source vehicle + * - p1 (bit 21) this is a virtual vehicle (for creating TemplateVehicles) * @param p2 what wagon to put the source wagon AFTER, XXX - INVALID_VEHICLE to make a new line * @param text unused * @return the cost of this operation or an error @@ -1222,10 +1226,16 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, u if (!move_chain && dst != NULL && dst->IsRearDualheaded() && src == dst->other_multiheaded_part) return CommandCost(); /* Check if all vehicles in the source train are stopped inside a depot. */ - if (!src_head->IsStoppedInDepot()) return_cmd_error(STR_ERROR_TRAINS_CAN_ONLY_BE_ALTERED_INSIDE_A_DEPOT); + /* Do this check only if the vehicle to be moved is non-virtual */ + if (!HasBit(p1, 21)) { + if (!src_head->IsStoppedInDepot()) return_cmd_error(STR_ERROR_TRAINS_CAN_ONLY_BE_ALTERED_INSIDE_A_DEPOT); + } /* Check if all vehicles in the destination train are stopped inside a depot. */ - if (dst_head != NULL && !dst_head->IsStoppedInDepot()) return_cmd_error(STR_ERROR_TRAINS_CAN_ONLY_BE_ALTERED_INSIDE_A_DEPOT); + /* Do this check only if the destination vehicle is non-virtual */ + if (!HasBit(p1, 21)) { + if (dst_head != NULL && !dst_head->IsStoppedInDepot()) return_cmd_error(STR_ERROR_TRAINS_CAN_ONLY_BE_ALTERED_INSIDE_A_DEPOT); + } /* First make a backup of the order of the trains. That way we can do * whatever we want with the order and later on easily revert. */ @@ -1334,8 +1344,11 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, u if (dst_head != NULL) dst_head->First()->MarkDirty(); /* We are undoubtedly changing something in the depot and train list. */ - InvalidateWindowData(WC_VEHICLE_DEPOT, src->tile); - InvalidateWindowClassesData(WC_TRAINS_LIST, 0); + /* But only if the moved vehicle is not virtual */ + if (!HasBit(src->subtype, GVSF_VIRTUAL)) { + InvalidateWindowData(WC_VEHICLE_DEPOT, src->tile); + InvalidateWindowClassesData(WC_TRAINS_LIST, 0); + } } else { /* We don't want to execute what we're just tried. */ RestoreTrainBackup(original_src); @@ -1418,8 +1431,11 @@ CommandCost CmdSellRailWagon(DoCommandFlag flags, Vehicle *t, uint16 data, uint3 NormaliseTrainHead(new_head); /* We are undoubtedly changing something in the depot and train list. */ - InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); - InvalidateWindowClassesData(WC_TRAINS_LIST, 0); + /* Unless its a virtual train */ + if (!HasBit(v->subtype, GVSF_VIRTUAL)) { + InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); + InvalidateWindowClassesData(WC_TRAINS_LIST, 0); + } /* Actually delete the sold 'goods' */ delete sell_head; @@ -3741,6 +3757,16 @@ static bool TrainCheckIfLineEnds(Train *v, bool reverse) return true; } +/* Calculate the summed up value of all parts of a train */ +Money Train::CalculateCurrentOverallValue() const +{ + Money ovr_value = 0; + const Train *v = this; + do { + ovr_value += v->value; + } while ((v = v->GetNextVehicle()) != NULL); + return ovr_value; +} static bool TrainLocoHandler(Train *v, bool mode) { @@ -4035,3 +4061,389 @@ Trackdir Train::GetVehicleTrackdir() const return TrackDirectionToTrackdir(FindFirstTrack(this->track), this->direction); } + +/* Get the pixel-width of the image that is used for the train vehicle + * @return: the image width number in pixel + */ +int GetDisplayImageWidth(Train *t, Point *offset) +{ + int reference_width = TRAININFO_DEFAULT_VEHICLE_WIDTH; + int vehicle_pitch = 0; + + const Engine *e = Engine::Get(t->engine_type); + if (e->grf_prop.grffile != NULL && is_custom_sprite(e->u.rail.image_index)) { + reference_width = e->grf_prop.grffile->traininfo_vehicle_width; + vehicle_pitch = e->grf_prop.grffile->traininfo_vehicle_pitch; + } + + if (offset != NULL) { + offset->x = reference_width / 2; + offset->y = vehicle_pitch; + } + return t->gcache.cached_veh_length * reference_width / VEHICLE_LENGTH; +} + +Train* CmdBuildVirtualRailWagon(const Engine *e) +{ + const RailVehicleInfo *rvi = &e->u.rail; + + Train *v = new Train(); + + v->x_pos = 0; + v->y_pos = 0; + + v->spritenum = rvi->image_index; + + v->engine_type = e->index; + v->gcache.first_engine = INVALID_ENGINE; // needs to be set before first callback + + v->direction = DIR_W; + v->tile = 0; // INVALID_TILE; + + v->owner = _current_company; + v->track = TRACK_BIT_DEPOT; + v->vehstatus = VS_HIDDEN | VS_DEFPAL; + + v->SetWagon(); + v->SetFreeWagon(); + + v->cargo_type = e->GetDefaultCargoType(); + v->cargo_cap = rvi->capacity; + + v->railtype = rvi->railtype; + + v->build_year = _cur_year; + v->cur_image = SPR_IMG_QUERY; + v->random_bits = VehicleRandomBits(); + + v->group_id = DEFAULT_GROUP; + + AddArticulatedParts(v); + + // Make sure we set EVERYTHING to virtual, even articulated parts. + for (Train* train_part = v; train_part != NULL; train_part = train_part->Next()) { + train_part->SetVirtual(); + } + + _new_vehicle_id = v->index; + + v->UpdateViewport(true, false); + + v->First()->ConsistChanged(CCF_ARRANGE); + + CheckConsistencyOfArticulatedVehicle(v); + + return v; +} + +/** + * Build a railroad vehicle. + * @param tile tile of the depot where rail-vehicle is built. + * @param flags type of operation. + * @param e the engine to build. + * @param data bit 0 prevents any free cars from being added to the train. + * @param ret[out] the vehicle that has been built. + * @return the cost of this operation or an error. + */ +Train* CmdBuildVirtualRailVehicle(EngineID eid) +{ + if (!IsEngineBuildable(eid, VEH_TRAIN, _current_company)) { + return NULL; + } + + const Engine* e = Engine::Get(eid); + const RailVehicleInfo *rvi = &e->u.rail; + + int num_vehicles = (e->u.rail.railveh_type == RAILVEH_MULTIHEAD ? 2 : 1) + CountArticulatedParts(eid, false); + if (!Train::CanAllocateItem(num_vehicles)) { + return NULL; + } + + if (rvi->railveh_type == RAILVEH_WAGON) { + return CmdBuildVirtualRailWagon(e); + } + + Train *v = new Train(); + + v->x_pos = 0; + v->y_pos = 0; + + v->direction = DIR_W; + v->tile = 0; // INVALID_TILE; + v->owner = _current_company; + v->track = TRACK_BIT_DEPOT; + v->vehstatus = VS_HIDDEN | VS_STOPPED | VS_DEFPAL; + v->spritenum = rvi->image_index; + v->cargo_type = e->GetDefaultCargoType(); + v->cargo_cap = rvi->capacity; + v->last_station_visited = INVALID_STATION; + + v->engine_type = e->index; + v->gcache.first_engine = INVALID_ENGINE; // needs to be set before first callback + + v->reliability = e->reliability; + v->reliability_spd_dec = e->reliability_spd_dec; + v->max_age = e->GetLifeLengthInDays(); + + v->railtype = rvi->railtype; + _new_vehicle_id = v->index; + + v->cur_image = SPR_IMG_QUERY; + v->random_bits = VehicleRandomBits(); + + v->group_id = DEFAULT_GROUP; + + v->SetFrontEngine(); + v->SetEngine(); + + v->UpdateViewport(true, false); + + if (rvi->railveh_type == RAILVEH_MULTIHEAD) { + AddRearEngineToMultiheadedTrain(v); + } else { + AddArticulatedParts(v); + } + + // Make sure we set EVERYTHING to virtual, even articulated parts. + for (Train* train_part = v; train_part != NULL; train_part = train_part->Next()) { + train_part->SetVirtual(); + } + + v->ConsistChanged(CCF_ARRANGE); + + CheckConsistencyOfArticulatedVehicle(v); + + return v; +} + +/** + * Build a virtual train vehicle. + * @param tile unused + * @param flags type of operation + * @param p1 the engine ID to build + * @param p2 unused + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdBuildVirtualRailVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + EngineID eid = p1; + + bool should_execute = (flags & DC_EXEC) != 0; + + if (should_execute) { + Train* train = CmdBuildVirtualRailVehicle(eid); + + if (train == NULL) { + return CMD_ERROR; + } + } + + return CommandCost(); +} + +/** +* Replace a vehicle based on a template replacement order. +* @param tile unused +* @param flags type of operation +* @param p1 the ID of the vehicle to replace. +* @param p2 whether the vehicle should stay in the depot. +* @param text unused +* @return the cost of this operation or an error +*/ +CommandCost CmdTemplateReplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + VehicleID vehicle_id = p1; + + Vehicle* vehicle = Vehicle::GetIfValid(vehicle_id); + + if (vehicle == NULL || vehicle->type != VEH_TRAIN) { + return CMD_ERROR; + } + + bool should_execute = (flags & DC_EXEC) != 0; + + if (!should_execute) { + return CommandCost(); + } + + Train* incoming = Train::From(vehicle); + bool stayInDepot = p2 != 0; + + Train *new_chain = NULL; + Train *remainder_chain = NULL; + Train *tmp_chain = NULL; + TemplateVehicle *tv = GetTemplateVehicleByGroupID(incoming->group_id); + EngineID eid = tv->engine_type; + + CommandCost buy(EXPENSES_NEW_VEHICLES); + CommandCost move_cost(EXPENSES_NEW_VEHICLES); + CommandCost tmp_result(EXPENSES_NEW_VEHICLES); + + + /* first some tests on necessity and sanity */ + if (tv == NULL) return buy; + bool need_replacement = !TrainMatchesTemplate(incoming, tv); + bool need_refit = !TrainMatchesTemplateRefit(incoming, tv); + bool use_refit = tv->refit_as_template; + CargoID store_refit_ct = CT_INVALID; + short store_refit_csubt = 0; + // if a train shall keep its old refit, store the refit setting of its first vehicle + if (!use_refit) { + for (Train *getc = incoming; getc != NULL; getc = getc->GetNextUnit()) { + if (getc->cargo_type != CT_INVALID) { + store_refit_ct = getc->cargo_type; + break; + } + } + } + + // TODO: set result status to success/no success before returning + if (!need_replacement) { + if (!need_refit || !use_refit) { + /* before returning, release incoming train first if 2nd param says so */ + if (!stayInDepot) incoming->vehstatus &= ~VS_STOPPED; + return buy; + } + } else { + CommandCost buyCost = TestBuyAllTemplateVehiclesInChain(tv, tile); + if (!buyCost.Succeeded() || !CheckCompanyHasMoney(buyCost)) { + if (!stayInDepot) incoming->vehstatus &= ~VS_STOPPED; + return_cmd_error(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY); + } + } + + /* define replacement behavior */ + bool reuseDepot = tv->IsSetReuseDepotVehicles(); + bool keepRemainders = tv->IsSetKeepRemainingVehicles(); + + if (need_replacement) { + // step 1: generate primary for newchain and generate remainder_chain + // 1. primary of incoming might already fit the template + // leave incoming's primary as is and move the rest to a free chain = remainder_chain + // 2. needed primary might be one of incoming's member vehicles + // 3. primary might be available as orphan vehicle in the depot + // 4. we need to buy a new engine for the primary + // all options other than 1. need to make sure to copy incoming's primary's status + if (eid == incoming->engine_type) { // 1 + new_chain = incoming; + remainder_chain = incoming->GetNextUnit(); + if (remainder_chain) { + move_cost.AddCost(CmdMoveRailVehicle(tile, flags, remainder_chain->index | (1 << 20), INVALID_VEHICLE, 0)); + } + } else if ((tmp_chain = ChainContainsEngine(eid, incoming)) && tmp_chain != NULL) { // 2 + // new_chain is the needed engine, move it to an empty spot in the depot + new_chain = tmp_chain; + move_cost.AddCost(DoCommand(tile, new_chain->index, INVALID_VEHICLE, flags, CMD_MOVE_RAIL_VEHICLE)); + remainder_chain = incoming; + } else if (reuseDepot && (tmp_chain = DepotContainsEngine(tile, eid, incoming)) && tmp_chain != NULL) { // 3 + new_chain = tmp_chain; + move_cost.AddCost(DoCommand(tile, new_chain->index, INVALID_VEHICLE, flags, CMD_MOVE_RAIL_VEHICLE)); + remainder_chain = incoming; + } else { // 4 + tmp_result = DoCommand(tile, eid, 0, flags, CMD_BUILD_VEHICLE); + /* break up in case buying the vehicle didn't succeed */ + if (!tmp_result.Succeeded()) { + return tmp_result; + } + buy.AddCost(tmp_result); + new_chain = Train::Get(_new_vehicle_id); + /* make sure the newly built engine is not attached to any free wagons inside the depot */ + move_cost.AddCost(DoCommand(tile, new_chain->index, INVALID_VEHICLE, flags, CMD_MOVE_RAIL_VEHICLE)); + /* prepare the remainder chain */ + remainder_chain = incoming; + } + // If we bought a new engine or reused one from the depot, copy some parameters from the incoming primary engine + if (incoming != new_chain && flags == DC_EXEC) { + CopyHeadSpecificThings(incoming, new_chain, flags); + NeutralizeStatus(incoming); + + // additionally, if we don't want to use the template refit, refit as incoming + // the template refit will be set further down, if we use it at all + if (!use_refit) { + uint32 cb = GetCmdRefitVeh(new_chain); + DoCommand(new_chain->tile, new_chain->index, store_refit_ct | store_refit_csubt << 8 | 1 << 16 | (1 << 5), flags, cb); + } + } + + // step 2: fill up newchain according to the template + // foreach member of template (after primary): + // 1. needed engine might be within remainder_chain already + // 2. needed engine might be orphaned within the depot (copy status) + // 3. we need to buy (again) (copy status) + TemplateVehicle *cur_tmpl = tv->GetNextUnit(); + Train *last_veh = new_chain; + while (cur_tmpl) { + // 1. engine contained in remainder chain + if ((tmp_chain = ChainContainsEngine(cur_tmpl->engine_type, remainder_chain)) && tmp_chain != NULL) { + // advance remainder_chain (if necessary) to not lose track of it + if (tmp_chain == remainder_chain) { + remainder_chain = remainder_chain->GetNextUnit(); + } + move_cost.AddCost(CmdMoveRailVehicle(tile, flags, tmp_chain->index, last_veh->index, 0)); + } + // 2. engine contained somewhere else in the depot + else if (reuseDepot && (tmp_chain = DepotContainsEngine(tile, cur_tmpl->engine_type, new_chain)) && tmp_chain != NULL) { + move_cost.AddCost(CmdMoveRailVehicle(tile, flags, tmp_chain->index, last_veh->index, 0)); + } + // 3. must buy new engine + else { + tmp_result = DoCommand(tile, cur_tmpl->engine_type, 0, flags, CMD_BUILD_VEHICLE); + if (!tmp_result.Succeeded()) { + return tmp_result; + } + buy.AddCost(tmp_result); + tmp_chain = Train::Get(_new_vehicle_id); + move_cost.AddCost(CmdMoveRailVehicle(tile, flags, tmp_chain->index, last_veh->index, 0)); + } + // TODO: is this enough ? might it be that we bought a new wagon here and it now has std refit ? + if (need_refit && flags == DC_EXEC) { + if (use_refit) { + uint32 cb = GetCmdRefitVeh(tmp_chain); + DoCommand(tmp_chain->tile, tmp_chain->index, cur_tmpl->cargo_type | (cur_tmpl->cargo_subtype << 8) | (1 << 16) | (1 << 5), flags, cb); + } else { + uint32 cb = GetCmdRefitVeh(tmp_chain); + DoCommand(tmp_chain->tile, tmp_chain->index, store_refit_ct | (store_refit_csubt << 8) | (1 << 16) | (1 << 5), flags, cb); + } + } + cur_tmpl = cur_tmpl->GetNextUnit(); + last_veh = tmp_chain; + } + } + /* no replacement done */ + else { + new_chain = incoming; + } + /// step 3: reorder and neutralize the remaining vehicles from incoming + // wagons remaining from remainder_chain should be filled up in as few freewagonchains as possible + // each locos might be left as singular in the depot + // neutralize each remaining engine's status + + // refit, only if the template option is set so + if (use_refit && (need_refit || need_replacement)) { + CmdRefitTrainFromTemplate(new_chain, tv, flags); + } + + if (new_chain && remainder_chain) { + for (Train *ct = remainder_chain; ct; ct = ct->GetNextUnit()) { + TransferCargoForTrain(ct, new_chain); + } + } + + // point incoming to the newly created train so that starting/stopping from the calling function can be done + incoming = new_chain; + if (!stayInDepot && flags == DC_EXEC) { + new_chain->vehstatus &= ~VS_STOPPED; + } + + if (remainder_chain && keepRemainders && flags == DC_EXEC) { + BreakUpRemainders(remainder_chain); + } else if (remainder_chain) { + buy.AddCost(DoCommand(tile, remainder_chain->index | (1 << 20), 0, flags, CMD_SELL_VEHICLE)); + } + + /* Redraw main gui for changed statistics */ + SetWindowClassesDirty(WC_TEMPLATEGUI_MAIN); + + return buy; +} diff --git a/src/vehicle.cpp b/src/vehicle.cpp index a482520f25..9f17876dfe 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -52,6 +52,7 @@ #include "gamelog.h" #include "linkgraph/linkgraph.h" #include "linkgraph/refresh.h" +#include "tbtr_template_vehicle_func.h" #include "table/strings.h" @@ -626,6 +627,13 @@ void ResetVehicleColourMap() typedef SmallMap AutoreplaceMap; static AutoreplaceMap _vehicles_to_autoreplace; +/** + * List of vehicles that are issued for template replacement this tick. + * Mapping is {vehicle : leave depot after replacement} + */ +typedef SmallMap TemplateReplacementMap; +static TemplateReplacementMap _vehicles_to_templatereplace; + void InitializeVehicles() { _vehicles_to_autoreplace.Reset(); @@ -828,8 +836,18 @@ Vehicle::~Vehicle() */ void VehicleEnteredDepotThisTick(Vehicle *v) { - /* Vehicle should stop in the depot if it was in 'stopping' state */ - _vehicles_to_autoreplace[v] = !(v->vehstatus & VS_STOPPED); + /* Template Replacement Setup stuff */ + bool stayInDepot = v->current_order.GetDepotActionType(); + TemplateReplacement *tr = GetTemplateReplacementByGroupID(v->group_id); + if (tr != NULL) { + _vehicles_to_templatereplace[(Train*) v] = stayInDepot; + } else { + /* Moved the assignment for auto replacement here to prevent auto replacement + * from happening if template replacement is also scheduled */ + + /* Vehicle should stop in the depot if it was in 'stopping' state */ + _vehicles_to_autoreplace[v] = !(v->vehstatus & VS_STOPPED); + } /* We ALWAYS set the stopped state. Even when the vehicle does not plan on * stopping in the depot, so we stop it to ensure that it will not reserve @@ -874,9 +892,29 @@ static void RunVehicleDayProc() } } +static void ShowAutoReplaceAdviceMessage(const CommandCost &res, const Vehicle *v) +{ + StringID error_message = res.GetErrorMessage(); + if (error_message == STR_ERROR_AUTOREPLACE_NOTHING_TO_DO || error_message == INVALID_STRING_ID) return; + + if (error_message == STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY) error_message = STR_ERROR_AUTOREPLACE_MONEY_LIMIT; + + StringID message; + if (error_message == STR_ERROR_TRAIN_TOO_LONG_AFTER_REPLACEMENT) { + message = error_message; + } else { + message = STR_NEWS_VEHICLE_AUTORENEW_FAILED; + } + + SetDParam(0, v->index); + SetDParam(1, error_message); + AddVehicleAdviceNewsItem(message, v->index); +} + void CallVehicleTicks() { _vehicles_to_autoreplace.Clear(); + _vehicles_to_templatereplace.Clear(); RunVehicleDayProc(); @@ -953,6 +991,7 @@ void CallVehicleTicks() } } + /* do Auto Replacement */ Backup cur_company(_current_company, FILE_LINE); for (AutoreplaceMap::iterator it = _vehicles_to_autoreplace.Begin(); it != _vehicles_to_autoreplace.End(); it++) { v = it->first; @@ -981,24 +1020,39 @@ void CallVehicleTicks() continue; } - StringID error_message = res.GetErrorMessage(); - if (error_message == STR_ERROR_AUTOREPLACE_NOTHING_TO_DO || error_message == INVALID_STRING_ID) continue; + ShowAutoReplaceAdviceMessage(res, v); + } + cur_company.Restore(); - if (error_message == STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY) error_message = STR_ERROR_AUTOREPLACE_MONEY_LIMIT; + /* do Template Replacement */ + Backup tmpl_cur_company(_current_company, FILE_LINE); + for (TemplateReplacementMap::iterator it = _vehicles_to_templatereplace.Begin(); it != _vehicles_to_templatereplace.End(); it++) { + Train *t = it->first; - StringID message; - if (error_message == STR_ERROR_TRAIN_TOO_LONG_AFTER_REPLACEMENT) { - message = error_message; - } else { - message = STR_NEWS_VEHICLE_AUTORENEW_FAILED; + /* Store the position of the effect as the vehicle pointer will become invalid later */ + int x = t->x_pos; + int y = t->y_pos; + int z = t->z_pos; + + tmpl_cur_company.Change(t->owner); + + bool stayInDepot = it->second; + + it->first->vehstatus |= VS_STOPPED; + CommandCost res = DoCommand(t->tile, t->index, stayInDepot ? 1 : 0, DC_EXEC, CMD_TEMPLATE_REPLACE_VEHICLE); + + if (!IsLocalCompany()) continue; + + if (res.Succeeded()) { + if (res.GetCost() != 0) { + ShowCostOrIncomeAnimation(x, y, z, res.GetCost()); + } + continue; } - SetDParam(0, v->index); - SetDParam(1, error_message); - AddVehicleAdviceNewsItem(message, v->index); + ShowAutoReplaceAdviceMessage(res, t); } - - cur_company.Restore(); + tmpl_cur_company.Restore(); } /** diff --git a/src/vehicle_base.h b/src/vehicle_base.h index 59584da788..d920246a43 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -26,6 +26,8 @@ #include #include +CommandCost CmdRefitVehicle(TileIndex, DoCommandFlag, uint32, uint32, const char*); + /** Vehicle status bits in #Vehicle::vehstatus. */ enum VehStatus { VS_HIDDEN = 0x01, ///< Vehicle is not visible. @@ -115,6 +117,7 @@ enum GroundVehicleSubtypeFlags { GVSF_ENGINE = 3, ///< Engine that can be front engine, but might be placed behind another engine (not used for road vehicles). GVSF_FREE_WAGON = 4, ///< First in a wagon chain (in depot) (not used for road vehicles). GVSF_MULTIHEADED = 5, ///< Engine is multiheaded (not used for road vehicles). + GVSF_VIRTUAL = 6, ///< Used for virtual trains during template design, it is needed to skip checks for tile or depot status }; /** Cached often queried values common to all vehicles. */ @@ -514,6 +517,7 @@ public: Money GetDisplayProfitLastYear() const { return (this->profit_last_year >> 8); } void SetNext(Vehicle *next); + inline void SetFirst(Vehicle *f) { this->first = f; } /** * Get the next vehicle of this vehicle. diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index 3b664defb6..622cf0d562 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -31,6 +31,8 @@ #include "ship.h" #include "newgrf.h" #include "company_base.h" +#include "tbtr_template_vehicle.h" +#include "tbtr_template_vehicle_func.h" #include "table/strings.h" @@ -174,7 +176,10 @@ CommandCost CmdSellVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 if (front->vehstatus & VS_CRASHED) return_cmd_error(STR_ERROR_VEHICLE_IS_DESTROYED); - if (!front->IsStoppedInDepot()) return_cmd_error(STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT + front->type); + /* Do this check only if the vehicle to be moved is non-virtual */ + if (!HasBit(p1, 21)) { + if (!front->IsStoppedInDepot()) return_cmd_error(STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT + front->type); + } /* Can we actually make the order backup, i.e. are there enough orders? */ if (p1 & MAKE_ORDER_BACKUP_FLAG && @@ -421,6 +426,7 @@ static CommandCost RefitVehicle(Vehicle *v, bool only_this, uint8 num_vehicles, * @param p1 vehicle ID to refit * @param p2 various bitstuffed elements * - p2 = (bit 0-4) - New cargo type to refit to. + * - p2 = (bit 5) - Is a virtual train (used by template replacement to allow refitting without stopped-in-depot checks) * - p2 = (bit 6) - Automatic refitting. * - p2 = (bit 7) - Refit only this vehicle. Used only for cloning vehicles. * - p2 = (bit 8-15) - New cargo subtype to refit to. 0xFF means to try keeping the same subtype according to GetBestFittingSubType(). @@ -444,12 +450,19 @@ CommandCost CmdRefitVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint if (ret.Failed()) return ret; bool auto_refit = HasBit(p2, 6); + bool is_virtual_train = HasBit(p2, 5); bool free_wagon = v->type == VEH_TRAIN && Train::From(front)->IsFreeWagon(); // used by autoreplace/renew /* Don't allow shadows and such to be refitted. */ if (v != front && (v->type == VEH_SHIP || v->type == VEH_AIRCRAFT)) return CMD_ERROR; /* Allow auto-refitting only during loading and normal refitting only in a depot. */ + if (!is_virtual_train) { + if (!free_wagon && (!auto_refit || !front->current_order.IsType(OT_LOADING)) && !front->IsStoppedInDepot()) { + return_cmd_error(STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT + front->type); + } + if (front->vehstatus & VS_CRASHED) return_cmd_error(STR_ERROR_VEHICLE_IS_DESTROYED); + } if ((flags & DC_QUERY_COST) == 0 && // used by the refit GUI, including the order refit GUI. !free_wagon && // used by autoreplace/renew (!auto_refit || !front->current_order.IsType(OT_LOADING)) && // refit inside stations @@ -499,7 +512,12 @@ CommandCost CmdRefitVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint InvalidateWindowData(WC_VEHICLE_DETAILS, front->index); InvalidateWindowClassesData(GetWindowClassForVehicleType(v->type), 0); } - SetWindowDirty(WC_VEHICLE_DEPOT, front->tile); + /* virtual vehicles get their cargo changed by the TemplateCreateWindow, so set this dirty instead of a depot window */ + if (HasBit(v->subtype, GVSF_VIRTUAL)) { + SetWindowClassesDirty(WC_CREATE_TEMPLATE); + } else { + SetWindowDirty(WC_VEHICLE_DEPOT, front->tile); + } } else { /* Always invalidate the cache; querycost might have filled it. */ v->InvalidateNewGRFCacheOfChain(); @@ -768,6 +786,453 @@ static void CloneVehicleName(const Vehicle *src, Vehicle *dst) /* All done. If we didn't find a name, it'll just use its default. */ } +inline 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; + tmp->value = virt->value; + + // 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; + + const GroundVehicleCache *gcache = virt->GetGroundVehicleCache(); + tmp->max_speed = virt->GetDisplayMaxSpeed(); + tmp->power = gcache->cached_power; + tmp->weight = gcache->cached_weight; + tmp->max_te = gcache->cached_max_te / 1000; + + tmp->spritenum = virt->spritenum; + tmp->cur_image = virt->GetImage(DIR_W, EIT_PURCHASE); + Point *p = new Point(); + tmp->image_width = virt->GetDisplayImageWidth(p); +} + +/** + * Toggles 'reuse depot vehicles' on a template vehicle. + * @param tile unused + * @param flags type of operation + * @param p1 the template vehicle's index + * @param p2 unused + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdToggleReuseDepotVehicles(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + // Identify template to toggle + TemplateVehicle *template_vehicle = TemplateVehicle::GetIfValid(p1); + + if (template_vehicle == NULL) { + return CMD_ERROR; + } + + bool should_execute = (flags & DC_EXEC) != 0; + + if (should_execute) { + template_vehicle->ToggleReuseDepotVehicles(); + + InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN, 0); + } + + return CommandCost(); +} + +/** + * Toggles 'keep remaining vehicles' on a template vehicle. + * @param tile unused + * @param flags type of operation + * @param p1 the template vehicle's index + * @param p2 unused + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdToggleKeepRemainingVehicles(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + // Identify template to toggle + TemplateVehicle *template_vehicle = TemplateVehicle::GetIfValid(p1); + + if (template_vehicle == NULL) { + return CMD_ERROR; + } + + bool should_execute = (flags & DC_EXEC) != 0; + + if (should_execute) { + template_vehicle->ToggleKeepRemainingVehicles(); + + InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN, 0); + } + + return CommandCost(); +} + +/** + * Toggles 'refit as template' on a template vehicle. + * @param tile unused + * @param flags type of operation + * @param p1 the template vehicle's index + * @param p2 unused + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdToggleRefitAsTemplate(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + // Identify template to toggle + TemplateVehicle *template_vehicle = TemplateVehicle::GetIfValid(p1); + + if (template_vehicle == NULL) { + return CMD_ERROR; + } + + bool should_execute = (flags & DC_EXEC) != 0; + + if (should_execute) { + template_vehicle->ToggleRefitAsTemplate(); + + InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN, 0); + } + + return CommandCost(); +} + +/** + * Create a virtual train from a template vehicle. + * @param tile unused + * @param flags type of operation + * @param p1 the original vehicle's index + * @param p2 unused + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdVirtualTrainFromTemplateVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + VehicleID template_vehicle_id = p1; + + TemplateVehicle* tv = TemplateVehicle::GetIfValid(template_vehicle_id); + + if (tv == NULL) { + return CMD_ERROR; + } + + bool should_execute = (flags & DC_EXEC) != 0; + + if (should_execute) { + Train* train = VirtualTrainFromTemplateVehicle(tv); + + if (train == NULL) { + return CMD_ERROR; + } + } + + return CommandCost(); +} + +Train* VirtualTrainFromTemplateVehicle(TemplateVehicle* tv) +{ + CommandCost c; + Train *tmp, *head, *tail; + + head = CmdBuildVirtualRailVehicle(tv->engine_type); + if (!head) return NULL; + + tail = head; + tv = tv->GetNextUnit(); + while (tv) { + tmp = CmdBuildVirtualRailVehicle(tv->engine_type); + if (tmp) { + tmp->cargo_type = tv->cargo_type; + tmp->cargo_subtype = tv->cargo_subtype; + CmdMoveRailVehicle(INVALID_TILE, DC_EXEC, (1 << 21) | tmp->index, tail->index, 0); + tail = tmp; + } + tv = tv->GetNextUnit(); + } + + _new_vehicle_id = head->index; + + return head; +} + +/** + * Create a virtual train from a regular train. + * @param tile unused + * @param flags type of operation + * @param p1 the train index + * @param p2 unused + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdVirtualTrainFromTrain(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + VehicleID vehicle_id = p1; + Vehicle* vehicle = Vehicle::GetIfValid(vehicle_id); + + if (vehicle == NULL || vehicle->type != VEH_TRAIN) { + return CMD_ERROR; + } + + Train* train = Train::From(vehicle); + + bool should_execute = (flags & DC_EXEC) != 0; + + if (should_execute) { + CommandCost c; + Train *tmp, *head, *tail; + + head = CmdBuildVirtualRailVehicle(train->engine_type); + if (!head) return CMD_ERROR; + + tail = head; + train = train->GetNextUnit(); + while (train) { + tmp = CmdBuildVirtualRailVehicle(train->engine_type); + if (tmp) { + tmp->cargo_type = train->cargo_type; + tmp->cargo_subtype = train->cargo_subtype; + CmdMoveRailVehicle(0, DC_EXEC, (1 << 21) | tmp->index, tail->index, 0); + tail = tmp; + } + train = train->GetNextUnit(); + } + + _new_vehicle_id = head->index; + } + + return CommandCost(); +} + +/** + * Create a virtual train from a template vehicle. + * @param tile unused + * @param flags type of operation + * @param p1 the vehicle's index + * @param p2 unused + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdDeleteVirtualTrain(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + VehicleID vehicle_id = p1; + + Vehicle* vehicle = Vehicle::GetIfValid(vehicle_id); + + if (vehicle == NULL || vehicle->type != VEH_TRAIN) { + return CMD_ERROR; + } + + Train* train = Train::From(vehicle); + + bool should_execute = (flags & DC_EXEC) != 0; + + if (should_execute) { + delete train; + } + + return CommandCost(); +} + +/** + * Replace a template vehicle with another one based on a virtual train. + * @param tile unused + * @param flags type of operation + * @param p1 the template vehicle's index + * @param p2 the virtual train's index + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdReplaceTemplateVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + VehicleID template_vehicle_id = p1; + VehicleID virtual_train_id = p2; + + TemplateVehicle* template_vehicle = TemplateVehicle::GetIfValid(template_vehicle_id); + Vehicle* vehicle = Vehicle::GetIfValid(virtual_train_id); + + if (vehicle == NULL || vehicle->type != VEH_TRAIN) { + return CMD_ERROR; + } + + Train* train = Train::From(vehicle); + + bool should_execute = (flags & DC_EXEC) != 0; + + if (should_execute) { + VehicleID old_ID = INVALID_VEHICLE; + + if (template_vehicle != NULL) { + old_ID = template_vehicle->index; + delete template_vehicle; + template_vehicle = NULL; + } + + template_vehicle = TemplateVehicleFromVirtualTrain(train); + + if (template_vehicle == NULL) { + return CMD_ERROR; + } + + // Make sure our replacements still point to the correct thing. + if (old_ID != template_vehicle->index) { + TemplateReplacement* tr; + FOR_ALL_TEMPLATE_REPLACEMENTS(tr) { + if (tr->GetTemplateVehicleID() == old_ID) { + tr->SetTemplate(template_vehicle->index); + } + } + } + + InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN, 0); + } + + return CommandCost(); +} + +/** + * Clone a vehicle to create a template vehicle. + * @param tile unused + * @param flags type of operation + * @param p1 the original vehicle's index + * @param p2 unused + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdTemplateVehicleFromTrain(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + // create a new template from the clicked vehicle + TemplateVehicle *tv; + + Vehicle *t = Vehicle::GetIfValid(p1); + + Train *clicked = Train::GetIfValid(t->index); + if (!clicked) return CMD_ERROR; + + Train *init_clicked = clicked; + + int len = CountVehiclesInChain(clicked); + if (!TemplateVehicle::CanAllocateItem(len)) { + return CMD_ERROR; + } + + bool should_execute = (flags & DC_EXEC) != 0; + + if (should_execute) { + TemplateVehicle *tmp; + TemplateVehicle *prev = NULL; + for (; clicked != NULL; clicked = clicked->Next()) { + tmp = new TemplateVehicle(clicked->engine_type); + SetupTemplateVehicleFromVirtual(tmp, prev, clicked); + prev = tmp; + } + + tmp->First()->SetRealLength(CeilDiv(init_clicked->gcache.cached_total_length * 10, TILE_SIZE)); + tv = tmp->First(); + + if (!tv) return CMD_ERROR; + + InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN, 0); + } + + return CommandCost(); +} + +/** + * Delete a template vehicle. + * @param tile unused + * @param flags type of operation + * @param p1 the template vehicle's index + * @param p2 unused + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdDeleteTemplateVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + // Identify template to delete + TemplateVehicle *del = TemplateVehicle::GetIfValid(p1); + + if (del == NULL) return CMD_ERROR; + + bool should_execute = (flags & DC_EXEC) != 0; + + if (should_execute) { + // Remove a corresponding template replacement if existing + TemplateReplacement *tr = GetTemplateReplacementByTemplateID(del->index); + if (tr != NULL) { + delete tr; + } + + delete del; + + InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN, 0); + } + + return CommandCost(); +} + +/** + * Issues a template replacement for a vehicle group + * @param tile unused + * @param flags type of operation + * @param p1 the group index + * @param p2 the template vehicle's index + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdIssueTemplateReplacement(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + bool should_execute = (flags & DC_EXEC) != 0; + + GroupID group_id = p1; + TemplateID template_id = p2; + + if (should_execute) { + bool succeeded = IssueTemplateReplacement(group_id, template_id); + + if (!succeeded) { + return CMD_ERROR; + } + + InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN, 0); + } + + return CommandCost(); +} + +/** + * Deletes a template replacement from a vehicle group + * @param tile unused + * @param flags type of operation + * @param p1 the group index + * @param p2 unused + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdDeleteTemplateReplacement(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + bool should_execute = (flags & DC_EXEC) != 0; + + GroupID group_id = p1; + + if (should_execute) { + TemplateReplacement* tr = GetTemplateReplacementByGroupID(group_id); + if (tr != NULL) { + delete tr; + } + + InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN, 0); + } + + return CommandCost(); +} + + /** * Clone a vehicle. If it is a train, it will clone all the cars too * @param tile tile of the depot where the cloned vehicle is build @@ -1012,6 +1477,30 @@ CommandCost CmdSendVehicleToDepot(TileIndex tile, DoCommandFlag flags, uint32 p1 return v->SendToDepot(flags, (DepotCommand)(p1 & DEPOT_COMMAND_MASK)); } +/** + * Sets the vehicle unit number + * @param tile unused + * @param flags type of operation + * @param p1 vehicle ID to set number on + * @param p2 vehicle unit number + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdSetVehicleUnitNumber(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + Vehicle *v = Vehicle::GetIfValid(p1); + if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR; + + CommandCost ret = CheckOwnership(v->owner); + if (ret.Failed()) return ret; + + if (flags & DC_EXEC) { + v->unitnumber = (UnitID)p2; + } + + return CommandCost(); +} + /** * Give a custom name to your vehicle * @param tile unused diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 10f1d952da..e914ed0451 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -37,6 +37,7 @@ #include "engine_func.h" #include "station_base.h" #include "tilehighlight_func.h" +#include "tbtr_template_gui_main.h" #include "zoom_func.h" #include "safeguards.h" @@ -142,11 +143,14 @@ void BaseVehicleListWindow::BuildVehicleList() * @param show_group If true include group-related stuff. * @return Required size. */ -Dimension BaseVehicleListWindow::GetActionDropdownSize(bool show_autoreplace, bool show_group) +Dimension BaseVehicleListWindow::GetActionDropdownSize(bool show_autoreplace, bool show_group, bool show_template_replace) { Dimension d = {0, 0}; if (show_autoreplace) d = maxdim(d, GetStringBoundingBox(STR_VEHICLE_LIST_REPLACE_VEHICLES)); + if (show_autoreplace && show_template_replace) { + d = maxdim(d, GetStringBoundingBox(STR_TMPL_TEMPLATE_REPLACEMENT)); + } d = maxdim(d, GetStringBoundingBox(STR_VEHICLE_LIST_SEND_FOR_SERVICING)); d = maxdim(d, GetStringBoundingBox(this->vehicle_depot_name[this->vli.vtype])); @@ -164,11 +168,14 @@ Dimension BaseVehicleListWindow::GetActionDropdownSize(bool show_autoreplace, bo * @param show_group If true include group-related stuff. * @return Itemlist for dropdown */ -DropDownList *BaseVehicleListWindow::BuildActionDropdownList(bool show_autoreplace, bool show_group) +DropDownList *BaseVehicleListWindow::BuildActionDropdownList(bool show_autoreplace, bool show_group, bool show_template_replace) { DropDownList *list = new DropDownList(); if (show_autoreplace) *list->Append() = new DropDownListStringItem(STR_VEHICLE_LIST_REPLACE_VEHICLES, ADI_REPLACE, false); + if (show_autoreplace && show_template_replace) { + *list->Append() = new DropDownListStringItem(STR_TMPL_TEMPLATE_REPLACEMENT, ADI_TEMPLATE_REPLACE, false); + } *list->Append() = new DropDownListStringItem(STR_VEHICLE_LIST_SEND_FOR_SERVICING, ADI_SERVICE, false); *list->Append() = new DropDownListStringItem(this->vehicle_depot_name[this->vli.vtype], ADI_DEPOT, false); @@ -400,6 +407,7 @@ struct RefitWindow : public Window { VehicleID selected_vehicle; ///< First vehicle in the current selection. uint8 num_vehicles; ///< Number of selected vehicles. bool auto_refit; ///< Select cargo for auto-refitting. + bool is_virtual_train; ///< TemplateReplacement, whether the selected vehicle is virtual /** * Collects all (cargo, subcargo) refit options of a vehicle chain. @@ -581,11 +589,12 @@ struct RefitWindow : public Window { return &l[this->sel[1]]; } - RefitWindow(WindowDesc *desc, const Vehicle *v, VehicleOrderID order, bool auto_refit) : Window(desc) + RefitWindow(WindowDesc *desc, const Vehicle *v, VehicleOrderID order, bool auto_refit, bool is_virtual) : Window(desc) { this->sel[0] = -1; this->sel[1] = 0; this->auto_refit = auto_refit; + this->is_virtual_train = is_virtual; this->order = order; this->CreateNestedTree(); @@ -949,9 +958,12 @@ struct RefitWindow : public Window { if (this->order == INVALID_VEH_ORDER_ID) { bool delete_window = this->selected_vehicle == v->index && this->num_vehicles == UINT8_MAX; - if (DoCommandP(v->tile, this->selected_vehicle, this->cargo->cargo | this->cargo->subtype << 8 | this->num_vehicles << 16, GetCmdRefitVeh(v)) && delete_window) delete this; + if (DoCommandP(v->tile, this->selected_vehicle, this->cargo->cargo | this->cargo->subtype << 8 | this->num_vehicles << 16 | this->is_virtual_train << 5, + GetCmdRefitVeh(v)) && delete_window) { + delete this; + } } else { - if (DoCommandP(v->tile, v->index, this->cargo->cargo | this->order << 16, CMD_ORDER_REFIT)) delete this; + if (DoCommandP(v->tile, v->index, this->cargo->cargo | this->cargo->subtype << 8 | this->order << 16 | this->is_virtual_train << 5, CMD_ORDER_REFIT)) delete this; } } break; @@ -1032,10 +1044,10 @@ static WindowDesc _vehicle_refit_desc( * @param parent the parent window of the refit window * @param auto_refit Choose cargo for auto-refitting */ -void ShowVehicleRefitWindow(const Vehicle *v, VehicleOrderID order, Window *parent, bool auto_refit) +void ShowVehicleRefitWindow(const Vehicle *v, VehicleOrderID order, Window *parent, bool auto_refit, bool is_virtual_train) { DeleteWindowById(WC_VEHICLE_REFIT, v->index); - RefitWindow *w = new RefitWindow(&_vehicle_refit_desc, v, order, auto_refit); + RefitWindow *w = new RefitWindow(&_vehicle_refit_desc, v, order, auto_refit, is_virtual_train); w->parent = parent; } @@ -1513,7 +1525,7 @@ public: } case WID_VL_MANAGE_VEHICLES_DROPDOWN: { - Dimension d = this->GetActionDropdownSize(this->vli.type == VL_STANDARD, false); + Dimension d = this->GetActionDropdownSize(this->vli.type == VL_STANDARD, false, this->vli.vtype == VEH_TRAIN); d.height += padding.height; d.width += padding.width; *size = maxdim(*size, d); @@ -1638,7 +1650,8 @@ public: break; case WID_VL_MANAGE_VEHICLES_DROPDOWN: { - DropDownList *list = this->BuildActionDropdownList(VehicleListIdentifier(this->window_number).type == VL_STANDARD, false); + DropDownList *list = this->BuildActionDropdownList(VehicleListIdentifier(this->window_number).type == VL_STANDARD, + false, this->vli.vtype == VEH_TRAIN); ShowDropDownList(this, list, 0, WID_VL_MANAGE_VEHICLES_DROPDOWN); break; } @@ -1663,6 +1676,11 @@ public: case ADI_REPLACE: // Replace window ShowReplaceGroupVehicleWindow(ALL_GROUP, this->vli.vtype); break; + case ADI_TEMPLATE_REPLACE: + if (vli.vtype == VEH_TRAIN) { + ShowTemplateReplaceWindow(this->unitnumber_digits, this->resize.step_height); + } + break; case ADI_SERVICE: // Send for servicing case ADI_DEPOT: // Send to Depots DoCommandP(0, DEPOT_MASS_SEND | (index == ADI_SERVICE ? DEPOT_SERVICE : (DepotCommand)0), this->window_number, GetCmdSendToDepot(this->vli.vtype)); diff --git a/src/vehicle_gui.h b/src/vehicle_gui.h index 83e098dcd9..523fe381e0 100644 --- a/src/vehicle_gui.h +++ b/src/vehicle_gui.h @@ -19,7 +19,7 @@ #include "engine_type.h" #include "company_type.h" -void ShowVehicleRefitWindow(const Vehicle *v, VehicleOrderID order, Window *parent, bool auto_refit = false); +void ShowVehicleRefitWindow(const Vehicle *v, VehicleOrderID order, Window *parent, bool auto_refit = false, bool is_virtual_train = false); /** The tabs in the train details window */ enum TrainDetailsWindowTabs { diff --git a/src/vehicle_gui_base.h b/src/vehicle_gui_base.h index 1c03f7b34d..3b7a4a2884 100644 --- a/src/vehicle_gui_base.h +++ b/src/vehicle_gui_base.h @@ -27,6 +27,7 @@ struct BaseVehicleListWindow : public Window { VehicleListIdentifier vli; ///< Identifier of the vehicle list we want to currently show. enum ActionDropdownItem { + ADI_TEMPLATE_REPLACE, ADI_REPLACE, ADI_SERVICE, ADI_DEPOT, @@ -46,8 +47,8 @@ struct BaseVehicleListWindow : public Window { void DrawVehicleListItems(VehicleID selected_vehicle, int line_height, const Rect &r) const; void SortVehicleList(); void BuildVehicleList(); - Dimension GetActionDropdownSize(bool show_autoreplace, bool show_group); - DropDownList *BuildActionDropdownList(bool show_autoreplace, bool show_group); + Dimension GetActionDropdownSize(bool show_autoreplace, bool show_group, bool show_template_replace); + DropDownList *BuildActionDropdownList(bool show_autoreplace, bool show_group, bool show_template_replace); }; uint GetVehicleListHeight(VehicleType type, uint divisor = 1); diff --git a/src/vehiclelist.cpp b/src/vehiclelist.cpp index 7e42b25aa2..c063ae5698 100644 --- a/src/vehiclelist.cpp +++ b/src/vehiclelist.cpp @@ -147,7 +147,7 @@ bool GenerateVehicleSortList(VehicleList *list, const VehicleListIdentifier &vli case VL_GROUP_LIST: if (vli.index != ALL_GROUP) { FOR_ALL_VEHICLES(v) { - if (v->type == vli.vtype && v->IsPrimaryVehicle() && + if (!HasBit(v->subtype, GVSF_VIRTUAL) && v->type == vli.vtype && v->IsPrimaryVehicle() && v->owner == vli.company && GroupIsInGroup(v->group_id, vli.index)) { *list->Append() = v; } @@ -158,7 +158,7 @@ bool GenerateVehicleSortList(VehicleList *list, const VehicleListIdentifier &vli case VL_STANDARD: FOR_ALL_VEHICLES(v) { - if (v->type == vli.vtype && v->owner == vli.company && v->IsPrimaryVehicle()) { + if (!HasBit(v->subtype, GVSF_VIRTUAL) && v->type == vli.vtype && v->owner == vli.company && v->IsPrimaryVehicle()) { *list->Append() = v; } } diff --git a/src/viewport.cpp b/src/viewport.cpp index a1bb2c81d1..2c353c7252 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -2189,9 +2189,10 @@ bool HandleViewportClicked(const ViewPort *vp, int x, int y) DEBUG(misc, 2, "Vehicle %d (index %d) at %p", v->unitnumber, v->index, v); if (IsCompanyBuildableVehicleType(v)) { v = v->First(); + WindowClass wc = _thd.GetCallbackWnd()->window_class; if (_ctrl_pressed && v->owner == _local_company) { StartStopVehicle(v, true); - } else { + } else if (wc != WC_CREATE_TEMPLATE && wc != WC_TEMPLATEGUI_MAIN) { ShowVehicleViewWindow(v); } } diff --git a/src/window_type.h b/src/window_type.h index 809e81d485..14d10598d9 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -681,6 +681,12 @@ enum WindowClass { */ WC_SAVE_PRESET, + WC_TEMPLATEGUI_MAIN, + WC_TEMPLATEGUI_RPLALL, + WC_BUILD_VIRTUAL_TRAIN, + WC_CREATE_TEMPLATE, + + WC_INVALID = 0xFFFF, ///< Invalid window. };