diff --git a/.gitignore b/.gitignore index 2b298a1..f371971 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ src/os/windows/ottdres.rc !/config.lib !*.in *.tmp + +*.swp diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj index 59e087e..f7a9e24 100644 --- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -291,6 +291,18 @@ + + + + + + + + + + + + @@ -869,6 +881,8 @@ + + diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters index 06800ff..f69240b 100644 --- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -102,6 +102,42 @@ + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + Source Files @@ -1836,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 70dcab2..70921e3 100644 --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -435,6 +435,54 @@ Name="Source Files" > + + + + + + + + + + + + + + + + + + + + + + + + @@ -2770,6 +2818,14 @@ RelativePath=".\..\src\saveload\waypoint_sl.cpp" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2767,6 +2815,14 @@ RelativePath=".\..\src\saveload\waypoint_sl.cpp" > + + + + 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 && _ctrl_pressed) { + DoCommandP(Vehicle::Get(sel)->tile, Vehicle::Get(sel)->index, true, CMD_REVERSE_TRAIN_DIRECTION | CMD_MSG(STR_ERROR_CAN_T_REVERSE_DIRECTION_RAIL_VEHICLE)); } else if (gdvp.wagon == NULL || gdvp.wagon->index != sel) { diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp index 12cce41..55873fb 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 361ab53..e513038 100644 --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -34,6 +34,8 @@ static const int LEVEL_WIDTH = 10; ///< Indenting width of a sub-group in pixels +#include "tbtr_template_gui_main.h" + typedef GUIList GUIGroupList; static const NWidgetPart _nested_group_widgets[] = { @@ -643,6 +645,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; } @@ -762,6 +765,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 +786,10 @@ 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 83b6591..6ee0740 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/newgrf.h b/src/newgrf.h index 1ab55dd..bcfd644 100644 --- a/src/newgrf.h +++ b/src/newgrf.h @@ -195,4 +195,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/newgrf_engine.cpp b/src/newgrf_engine.cpp index 8dd8d54..af54d24 100644 --- a/src/newgrf_engine.cpp +++ b/src/newgrf_engine.cpp @@ -1073,7 +1073,6 @@ void GetRotorOverrideSprite(EngineID engine, const struct Aircraft *v, bool info } } - /** * Check if a wagon is currently using a wagon override * @param v The wagon to check @@ -1329,3 +1328,4 @@ void FillNewGRFVehicleCache(const Vehicle *v) /* Make sure really all bits are set. */ assert(v->grf_cache.cache_valid == (1 << NCVV_END) - 1); } + diff --git a/src/newgrf_spritegroup.cpp b/src/newgrf_spritegroup.cpp index c6fcd0a..f1148f7 100644 --- a/src/newgrf_spritegroup.cpp +++ b/src/newgrf_spritegroup.cpp @@ -192,7 +192,6 @@ static uint32 RotateRight(uint32 val, uint32 rot) return (val >> rot) | (val << (32 - rot)); } - /* Evaluate an adjustment for a variable of the given size. * U is the unsigned type and S is the signed type to use. */ template diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 57b29f3..650d1ae 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 de3f7cc..8a15e97 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 fcda489..fddee00 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" @@ -449,6 +451,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[] = { @@ -485,6 +489,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, }; @@ -1245,6 +1251,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; @@ -1304,6 +1311,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 1513355..2e50992 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -90,6 +90,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 74e5b99..67b3038 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 0000000..9634e76 --- /dev/null +++ b/src/saveload/tbtr_template_replacement_sl.cpp @@ -0,0 +1,46 @@ +/* $Id: build_vehicle_gui.cpp 23792 2012-01-12 19:23:00Z yexo $ */ + +/* + * 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_replacement_sl.cpp Save and load template replacement instances. */ + +#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 0000000..faf41e8 --- /dev/null +++ b/src/saveload/tbtr_template_veh_sl.cpp @@ -0,0 +1,110 @@ +/* $Id: build_vehicle_gui.cpp 23792 2012-01-12 19:23:00Z yexo $ */ + +/* + * 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_veh_sl.cpp Save and load template vehicles. */ + +#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(); + 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 0000000..5717a52 --- /dev/null +++ b/src/tbtr_template_gui_create.cpp @@ -0,0 +1,438 @@ +/* $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 Window for the creation of template trains. */ + +#include "stdafx.h" + +#include "strings_func.h" +#include "tilehighlight_func.h" + +#include "tbtr_template_gui_create.h" +#include "tbtr_template_vehicle_func.h" + +class TemplateReplaceWindow; + +// some space in front of the virtual train in the matrix +uint16 TRAIN_FRONT_SPACE = 16; + +enum TemplateReplaceWindowWidgets { + TCW_CAPTION, + TCW_MATRIX_NEW_TMPL, + TCW_INFO_PANEL, + TCW_SCROLLBAR_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_STICKYBOX, COLOUR_GREY), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_VERTICAL), + NWidget(WWT_MATRIX, COLOUR_GREY, TCW_MATRIX_NEW_TMPL), SetMinimalSize(216, 60), SetFill(1, 0), SetDataTip(0x1, STR_REPLACE_HELP_LEFT_ARRAY), SetResize(1, 0), SetScrollbar(TCW_SCROLLBAR_NEW_TMPL), + NWidget(WWT_PANEL, COLOUR_GREY, TCW_INFO_PANEL), SetMinimalSize(216,80), SetResize(1,1), EndContainer(), + NWidget(NWID_HSCROLLBAR, COLOUR_GREY, TCW_SCROLLBAR_NEW_TMPL), SetResize(1,0), + EndContainer(), + NWidget(WWT_IMGBTN, COLOUR_GREY, TCW_SELL_TMPL), SetDataTip(0x0, STR_NULL), SetMinimalSize(23,23), SetResize(0, 1), SetFill(0, 1), + 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; + + CmdMoveRailVehicle(INVALID_TILE, DC_EXEC, (_ctrl_pressed ? 1:0)<<20 | (1<<21) | v->index, wagon == NULL ? INVALID_VEHICLE : wagon->index, 0); +} + +class TemplateCreateWindow : public Window { +private: + Scrollbar *hscroll; + 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 selected_train; /// the selected train in the GUI + 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); + this->hscroll = this->GetScrollbar(TCW_SCROLLBAR_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; + + if ( to_edit ) editMode = true; + else editMode = false; + + this->selected_train = INVALID_VEHICLE; + this->vehicle_over = INVALID_VEHICLE; + + this->virtual_train = VirtualTrainFromTemplateVehicle(to_edit); + + this->resize.step_height = 1; + } + ~TemplateCreateWindow() + { + if ( virtual_train ) + delete virtual_train; + + SetWindowClassesDirty(WC_TRAINS_LIST); + + /* more cleanup */ + *createWindowOpen = false; + DeleteWindowById(WC_BUILD_VIRTUAL_TRAIN, this->window_number); + + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + switch (widget) { + case TCW_MATRIX_NEW_TMPL: + size->height = 20; + break; + } + } + virtual void OnResize() + { + NWidgetCore *nwi = this->GetWidget(TCW_MATRIX_NEW_TMPL); + this->hscroll->SetCapacity(nwi->current_x); + nwi->widget_data = (this->hscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START); + } + + + virtual void OnInvalidateData(int data = 0, bool gui_scope = true) + { + virtualTrainChangedNotice = true; + } + virtual void OnClick(Point pt, int widget, int click_count) + { + switch(widget) { + case TCW_MATRIX_NEW_TMPL: { + NWidgetBase *nwi = this->GetWidget(TCW_MATRIX_NEW_TMPL); + ClickedOnVehiclePanel(pt.x - nwi->pos_x-TRAIN_FRONT_SPACE, 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)) { + static const CursorID clone_icon = SPR_CURSOR_CLONE_TRAIN; + SetObjectToPlaceWnd(clone_icon, PAL_NONE, HT_VEHICLE, this); + } else { + ResetObjectToPlace(); + } + break; + } + case TCW_OK: { + TemplateVehicle *tv = NULL; + if ( editMode ) tv = DeleteTemplateVehicle(editTemplate); + editTemplate = TemplateVehicleFromVirtualTrain(virtual_train); + if ( tv ) *noticeParent = true; + delete this; + break; + } + case TCW_CANCEL: { + delete this; + break; + } + case TCW_REFIT: { + if(this->virtual_train) { + 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 ) + delete this->virtual_train; + // create a new one + this->virtual_train = CloneVirtualTrainFromTrain((const Train*)v); + this->ToggleWidgetLoweredState(TCW_CLONE); + ResetObjectToPlace(); + this->SetDirty(); + + return true; + } + virtual void DrawWidget(const Rect &r, int widget) const + { + switch(widget) { + case TCW_MATRIX_NEW_TMPL: { + if ( this->virtual_train ) { + DrawTrainImage(virtual_train, r.left+TRAIN_FRONT_SPACE, r.right, r.top+2, this->selected_train, 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 ) { + /* 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(r.left+8, r.right, r.top+4, STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE); + /* Draw cargo summary */ + CargoArray cargo_caps; + for ( const Train *tmp=this->virtual_train; tmp; tmp=tmp->Next() ) + cargo_caps[tmp->cargo_type] += tmp->cargo_cap; + int y = r.top+24; + for (CargoID i = 0; i < NUM_CARGO; ++i) { + if ( cargo_caps[i] > 0 ) { + SetDParam(0, i); + SetDParam(1, cargo_caps[i]); + SetDParam(2, _settings_game.vehicle.freight_trains); + DrawString(r.left+8, r.right, y, STR_TMPL_CARGO_SUMMARY, TC_WHITE, SA_LEFT); + y += this->line_height/2; + } + } + } + break; + } + default: + break; + } + } + virtual void OnTick() + { + if ( virtualTrainChangedNotice ) { + this->SetDirty(); + virtualTrainChangedNotice = false; + } + } + virtual void OnDragDrop(Point pt, int widget) + { + switch (widget) { + case TCW_MATRIX_NEW_TMPL: { + const Vehicle *v = NULL; + VehicleID sel; + if ( virtual_train ) sel = virtual_train->index; + else sel = INVALID_VEHICLE; + + this->SetDirty(); + + NWidgetBase *nwi = this->GetWidget(TCW_MATRIX_NEW_TMPL); + 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); + virtual_train = virtual_train->First(); + } + } + break; + } + case TCW_SELL_TMPL: { + if (this->IsWidgetDisabled(widget)) return; + if (this->selected_train == INVALID_VEHICLE) return; + + this->virtual_train = DeleteVirtualTrain(this->virtual_train, Train::Get(this->selected_train)); + + this->selected_train = INVALID_VEHICLE; + + this->SetDirty(); + break; + } + default: + this->selected_train = INVALID_VEHICLE; + this->SetDirty(); + } + _cursor.vehchain = false; + this->selected_train = INVALID_VEHICLE; + this->SetDirty(); + } + virtual void OnMouseDrag(Point pt, int widget) + { + if (this->selected_train == INVALID_VEHICLE) return; + /* A rail vehicle is dragged.. */ + if (widget != TCW_MATRIX_NEW_TMPL) { // ..outside of the depot matrix. + if (this->vehicle_over != INVALID_VEHICLE) { + this->vehicle_over = INVALID_VEHICLE; + this->SetWidgetDirty(TCW_MATRIX_NEW_TMPL); + } + 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->selected_train) { // ..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->selected_train && + gdvp.wagon->Previous()->index != this->selected_train) { // ..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 max_width = 32; + uint width = 0; + if ( virtual_train ) + for (Train *v = virtual_train; v != NULL; v = v->Next()) + width += v->GetDisplayImageWidth(); + + max_width = max(max_width, width); + this->hscroll->SetCount(max_width+25); + + 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_MATRIX_NEW_TMPL); + /* In case of RTL the widgets are swapped as a whole */ + if (_current_text_dir == TD_RTL) x = matrix_widget->current_x - x; + + 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->selected_train; + + if (sel != INVALID_VEHICLE) { + this->selected_train = INVALID_VEHICLE; + } else if (v != NULL) { + SetObjectToPlaceWnd(SPR_CURSOR_MOUSE, PAL_NONE, HT_DRAG, this); + SetMouseCursorVehicle(v, EIT_IN_DEPOT); + _cursor.vehchain = _ctrl_pressed; + + this->selected_train = v->index; + this->SetDirty(); + } + } +}; + +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); +} + diff --git a/src/tbtr_template_gui_create.h b/src/tbtr_template_gui_create.h new file mode 100644 index 0000000..ad7868e --- /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 Window for the creation of template trains. */ + +#ifndef TEMPLATE_GUI_CREATE +#define TEMPLATE_GUI_CREATE + +#include "tbtr_template_gui_create_virtualtrain.h" +#include "tbtr_template_vehicle.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 0000000..b8bd732 --- /dev/null +++ b/src/tbtr_template_gui_create_virtualtrain.cpp @@ -0,0 +1,878 @@ +/* $Id: build_vehicle_gui.cpp 23792 2012-01-12 19:23:00Z yexo $ */ + +/* + * 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 Window to add more train parts to a template train. */ + +#include "stdafx.h" + +#include "window_func.h" + +#include "articulated_vehicles.h" +#include "command_func.h" +#include "company_func.h" +#include "core/geometry_func.hpp" +#include "engine_func.h" +#include "engine_gui.h" +#include "group.h" +#include "station_base.h" +#include "string_func.h" +#include "strings_func.h" +#include "vehicle_func.h" +#include "widgets/build_vehicle_widget.h" +#include "widgets/dropdown_func.h" + +#include "tbtr_template_gui_create_virtualtrain.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_VERTICAL), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SORT_ASCENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), + NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_SORT_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDDEN_ENGINES), + 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_SHOW_HIDE), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL), + 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_y_offsets[] = { -1, -1, -2, -2 }; + + /* Obligatory sanity checks! */ + assert(max <= eng_list->Length()); + + bool rtl = _current_text_dir == TD_RTL; + int step_size = GetEngineListHeight(type); + int sprite_left = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_left; + int sprite_right = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_right; + int sprite_width = sprite_left + sprite_right; + + int sprite_x = rtl ? r - sprite_right - 1 : l + sprite_left + 1; + int sprite_y_offset = sprite_y_offsets[type] + step_size / 2; + + Dimension replace_icon = {0, 0}; + int count_width = 0; + if (show_count) { + replace_icon = GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE); + SetDParamMaxDigits(0, 3, FS_SMALL); + count_width = GetStringBoundingBox(STR_TINY_BLACK_COMA).width; + } + + int text_left = l + (rtl ? WD_FRAMERECT_LEFT + replace_icon.width + 8 + count_width : sprite_width + WD_FRAMETEXT_LEFT); + int text_right = r - (rtl ? sprite_width + WD_FRAMETEXT_RIGHT : WD_FRAMERECT_RIGHT + replace_icon.width + 8 + count_width); + + int normal_text_y_offset = (step_size - FONT_HEIGHT_NORMAL) / 2; + + 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); + + const Engine *e = Engine::Get(engine); + bool hidden = HasBit(e->company_hidden, _local_company); + StringID str = hidden ? STR_HIDDEN_ENGINE_NAME : STR_ENGINE_NAME; + TextColour tc = (engine == selected_id) ? TC_WHITE : (TC_NO_SHADE | (hidden ? TC_GREY : TC_BLACK)); + + SetDParam(0, engine); + DrawString(text_left, text_right, y + normal_text_y_offset, str, tc); + DrawVehicleEngine(l, r, sprite_x, y + sprite_y_offset, engine, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(engine, _local_company), EIT_PURCHASE); + } +} + +struct BuildVirtualTrainWindow : Window { + VehicleType vehicle_type; + bool show_hidden_engines; + 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->show_hidden_engines = _engine_sort_show_hidden_engines[this->vehicle_type]; + this->window_number = 0;//tile == INVALID_TILE ? (int)type : tile; + + 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_SHOW_HIDE); + widget->tool_tip = STR_BUY_VEHICLE_TRAIN_HIDE_SHOW_TOGGLE_TOOLTIP + VEH_TRAIN; + + 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; + + widget = this->GetWidget(WID_BV_SHOW_HIDDEN_ENGINES); + widget->widget_data = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN + VEH_TRAIN; + widget->tool_tip = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP + VEH_TRAIN; + widget->SetLowered(this->show_hidden_engines); + + 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) { + if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue; + 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; + + /* XXX Between this and the train vehicle creation window, there are sync issues (will only rebuild list in one window once the button has been clicked for that particular window) */ + case WID_BV_SHOW_HIDDEN_ENGINES: + this->show_hidden_engines ^= true; + _engine_sort_show_hidden_engines[this->vehicle_type] = this->show_hidden_engines; + this->eng_list.ForceRebuild(); + this->SetWidgetLoweredState(widget, this->show_hidden_engines); + 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 (_ctrl_pressed) this->OnClick(pt, WID_BV_SHOW_HIDE, 1); + else 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_SHOW_HIDE: { + const Engine *e = (this->sel_engine == INVALID_ENGINE) ? NULL : Engine::Get(this->sel_engine); + if (e != NULL) { + DoCommandP(0, 0, this->sel_engine | (e->IsHidden(_current_company) ? 0 : (1u << 31)), CMD_SET_VEHICLE_VISIBILITY); + this->eng_list.ForceRebuild(); + } + break; + } + + case WID_BV_BUILD: { + EngineID sel_eng = this->sel_engine; + if (sel_eng != INVALID_ENGINE) { + Train *tmp = CmdBuildVirtualRailVehicle(sel_eng); + if (tmp) AddVirtualEngine(tmp); + } + 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. */ + if (this->vehicle_type == VEH_ROAD && + _settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL && + this->sort_criteria > 7) { + this->sort_criteria = 0; + _last_sort_criteria[VEH_ROAD] = 0; + } + 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; + + case WID_BV_SHOW_HIDE: { + const Engine *e = (this->sel_engine == INVALID_ENGINE) ? NULL : Engine::Get(this->sel_engine); + if (e != NULL && e->IsHidden(_local_company)) { + SetDParam(0, STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type); + } else { + SetDParam(0, STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type); + } + 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; + } + + case WID_BV_SHOW_HIDE: { + *size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type); + *size = maxdim(*size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type)); + size->width += padding.width; + size->height += padding.height; + 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->SetWidgetDisabledState(WID_BV_SHOW_HIDE, this->sel_engine == INVALID_ENGINE); + + 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 ) { + *virtual_train = toadd; + } + else { + VehicleID target = (*(this->virtual_train))->GetLastUnit()->index; + CommandCost movec; + movec = CmdMoveRailVehicle(INVALID_TILE, DC_EXEC, (1<<21) | toadd->index, target, 0); + } + *noticeParent = true; + } +}; + +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 0000000..add5019 --- /dev/null +++ b/src/tbtr_template_gui_create_virtualtrain.h @@ -0,0 +1,19 @@ +/* $Id: build_vehicle_gui.cpp 23792 2012-01-12 19:23:00Z yexo $ */ + +/* + * 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.h Window to add more train parts to a template train. */ + +#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 0000000..2d3b3b6 --- /dev/null +++ b/src/tbtr_template_gui_main.cpp @@ -0,0 +1,663 @@ +/* $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 Main window for template configuration and overview. */ + +#include "stdafx.h" + +#include "core/geometry_func.hpp" +#include "group.h" +#include "rail_gui.h" +#include "tilehighlight_func.h" +#include "vehicle_gui_base.h" + +#include "tbtr_template_gui_create.h" +#include "tbtr_template_gui_main.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_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_RPLALL, + TRW_WIDGET_TMPL_BUTTON_FLUFF, + 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_STICKYBOX, COLOUR_GREY), + EndContainer(), + //Top Matrix + NWidget(NWID_VERTICAL), + NWidget(WWT_INSET, COLOUR_GREY, TRW_WIDGET_INSET_GROUPS), SetMinimalSize(216,12), SetDataTip(STR_TMPL_MAINGUI_DEFINEDGROUPS, STR_TMPL_MAINGUI_DEFINEDGROUPS), 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_INSET, COLOUR_GREY, TRW_WIDGET_INSET_TEMPLATES), SetMinimalSize(216,12), SetDataTip(STR_TMPL_AVAILABLE_TEMPLATES, STR_TMPL_AVAILABLE_TEMPLATES), 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_BOTTOM_SCROLLBAR), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, TRW_WIDGET_BOTTOM_SCROLLBAR), + EndContainer(), + EndContainer(), + // Info Area + NWidget(NWID_VERTICAL), + NWidget(WWT_INSET, COLOUR_GREY, TRW_WIDGET_TMPL_INFO_INSET), SetMinimalSize(216,12), SetResize(1,0), SetDataTip(STR_TMPL_AVAILABLE_TEMPLATES, STR_TMPL_AVAILABLE_TEMPLATES), EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_TMPL_INFO_PANEL), SetMinimalSize(216,50), SetResize(1,0), 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_REPLACE_ENGINE_WAGON_SELECT_HELP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_EDIT), SetMinimalSize(75,12), SetResize(0,0), SetDataTip(STR_TMPL_EDIT_TEMPLATE, STR_REPLACE_ENGINE_WAGON_SELECT_HELP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_CLONE), SetMinimalSize(75,12), SetResize(0,0), SetDataTip(STR_TMPL_CREATE_CLONE_VEH, STR_REPLACE_ENGINE_WAGON_SELECT_HELP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_DELETE), SetMinimalSize(75,12), SetResize(0,0), SetDataTip(STR_TMPL_DELETE_TEMPLATE, STR_REPLACE_ENGINE_WAGON_SELECT_HELP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_RPLALL), SetMinimalSize(150,12), SetResize(0,0), SetDataTip(STR_TMPL_RPL_ALL_TMPL, STR_REPLACE_ENGINE_WAGON_SELECT_HELP), + 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; + + 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[2]; + // 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); + this->vscroll[0] = this->GetScrollbar(TRW_WIDGET_TOP_SCROLLBAR); + this->vscroll[1] = this->GetScrollbar(TRW_WIDGET_BOTTOM_SCROLLBAR); + this->vscroll[0]->SetStepSize(step_h / 2); + this->vscroll[1]->SetStepSize(step_h); + this->FinishInitNested(VEH_TRAIN); + + this->owner = _local_company; + + this->groups.ForceRebuild(); + this->groups.NeedResort(); + this->BuildGroupList(_local_company); + this->groups.Sort(&GroupNameSorter); + + + this->matrixContentLeftMargin = 40; + this->selected_template_index = -1; + this->selected_group_index = -1; + + 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; + } + } + } + + virtual void OnPaint() + { + BuildTemplateGuiList(&this->templates, this->vscroll[1], this->owner, this->sel_railtype); + + this->BuildGroupList(_local_company); + this->groups.Sort(&GroupNameSorter); + + 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; + + 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 ) { + TemplateVehicle *sel = TemplateVehicle::Get(((this->templates)[selected_template_index])->index); + sel->ToggleReuseDepotVehicles(); + } + break; + } + case TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_KEEP: { + if ( this->selected_template_index >= 0 ) { + TemplateVehicle *sel = TemplateVehicle::Get(((this->templates)[selected_template_index])->index); + sel->ToggleKeepRemainingVehicles(); + } + break; + } + case TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REFIT: { + if ( this->selected_template_index >= 0 ) { + TemplateVehicle *sel = TemplateVehicle::Get(((this->templates)[selected_template_index])->index); + sel->ToggleRefitAsTemplate(); + } + 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 ) { + 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 ( selected_template_index >= 0 && !editInProgress ) { + // identify template to delete + TemplateVehicle *del = TemplateVehicle::Get(((this->templates)[selected_template_index])->index); + // remove a corresponding template replacement if existing + TemplateReplacement *tr = GetTemplateReplacementByTemplateID(del->index); + if ( tr ) { + delete tr; + } + delete del; + BuildTemplateGuiList(&this->templates, this->vscroll[1], this->owner, this->sel_railtype); + selected_template_index = -1; + } + break; + case TRW_WIDGET_TMPL_BUTTONS_RPLALL: { + ShowTemplateReplaceAllGui(); + 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; + } + 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; + } + break; + } + case TRW_WIDGET_START: { + if ( this->selected_template_index >= 0 && this->selected_group_index >= 0) { + uint32 tv_index = ((this->templates)[selected_template_index])->index; + int current_group_index = (this->groups)[this->selected_group_index]->index; + IssueTemplateReplacement(current_group_index, tv_index); + } + break; + } + case TRW_WIDGET_STOP: + if ( this->selected_group_index == -1 ) + return; + int current_group_index = (this->groups)[this->selected_group_index]->index; + TemplateReplacement *tr = GetTemplateReplacementByGroupID(current_group_index); + if ( tr ) + delete tr; + break; + } + this->SetDirty(); + } + + virtual bool OnVehicleSelect(const Vehicle *v) + { + // create a new template from the clicked vehicle + TemplateVehicle *tv = CloneTemplateVehicleFromTrain((const Train*)v); + if ( !tv ) 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); + } + + 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(); + } + + /** 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; itemplates.Length(); ++i ) + if ( templates[i]->index == tr->sel_template ) + return i; + return -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(); + + const Group *g; + FOR_ALL_GROUPS(g) { + if (g->owner == owner ) { + *this->groups.Append() = g; + } + } + + 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(); igroups)[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, 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); + } + /* If there isn't a template applied from the current group, check if there is one for another rail type */ + else if ( GetTemplateReplacementByGroupID(g_id) ) { + 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 + line_height/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+200, right, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 2, STR_TMP_TEMPLATE_IN_USE, TC_GREEN, SA_LEFT); + } + + /* Draw information about template configuration settings */ + TextColour color; + if ( v->IsSetReuseDepotVehicles() ) color = TC_LIGHT_BLUE; + else color = TC_GREY; + DrawString(left+200, right, y+2, STR_TMPL_CONFIG_USEDEPOT, color, SA_LEFT); + if ( v->IsSetKeepRemainingVehicles() ) color = TC_LIGHT_BLUE; + else color = TC_GREY; + DrawString(left+275, right, y+2, STR_TMPL_CONFIG_KEEPREMAINDERS, color, SA_LEFT); + if ( v->IsSetRefitAsTemplate() ) color = TC_LIGHT_BLUE; + else color = TC_GREY; + DrawString(left+350, right, y+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 == -1 || (short)this->templates.Length() <= this->selected_template_index ) + return; + + 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(r.left+8, r.right, r.top+4, STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE); + + /* Draw cargo summary */ + short top = r.top + 24; + short left = r.left + 8; + short count_rows = 0; + short max_rows = 2; + + CargoArray cargo_caps; + for ( ; tmp; tmp=tmp->Next() ) + cargo_caps[tmp->cargo_type] += tmp->cargo_cap; + int y = top; + for (CargoID i = 0; i < NUM_CARGO; ++i) { + if ( cargo_caps[i] > 0 ) { + count_rows++; + SetDParam(0, i); + SetDParam(1, cargo_caps[i]); + SetDParam(2, _settings_game.vehicle.freight_trains); + DrawString(left, r.right, y, FreightWagonMult(i) > 1 ? STR_TMPL_CARGO_SUMMARY_MULTI : STR_TMPL_CARGO_SUMMARY, TC_WHITE, SA_LEFT); + y += this->line_height/2; + if ( count_rows % max_rows == 0 ) { + y = top; + left += 150; + } + } + } + } +}; + +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 0000000..9719750 --- /dev/null +++ b/src/tbtr_template_gui_main.h @@ -0,0 +1,29 @@ +/* $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 Main window for template configuration and overview. */ + +#ifndef TEMPLATE_GUI_H +#define TEMPLATE_GUI_H + +#include "engine_type.h" +#include "group_type.h" +#include "string_func.h" +#include "strings_func.h" +#include "vehicle_type.h" + +#include "tbtr_template_gui_replaceall.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_gui_replaceall.cpp b/src/tbtr_template_gui_replaceall.cpp new file mode 100644 index 0000000..4a95c89 --- /dev/null +++ b/src/tbtr_template_gui_replaceall.cpp @@ -0,0 +1,525 @@ +/* $Id: build_vehicle_gui.cpp 23792 2012-01-12 19:23:00Z yexo $ */ + +/* + * 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_replaceall.cpp Window to control the replacement of individual parts in all template trains. */ + +#include "tbtr_template_gui_replaceall.h" + +/* + * A wrapper which contains a virtual train and additional info of the template vehicle it is replacing + * We will restore this additional info when creating a new template from the changed virtual train + */ +struct VirtTrainInfo { + // the virtual train + Train *vt; + + // additional info from the template + VehicleID original_index; + + bool reuse_depot_vehicles, + keep_remaining_vehicles, + refit_as_template; + + CargoID cargo_type; + byte cargo_subtype; + + // a fancy constructor + VirtTrainInfo(Train *t) { this->vt = t; } +}; + +typedef AutoFreeSmallVector VirtTrainList; +enum Widgets { + RPLALL_GUI_CAPTION, + + RPLALL_GUI_INSET_1, + RPLALL_GUI_INSET_1_1, + RPLALL_GUI_INSET_1_2, + RPLALL_GUI_MATRIX_TOPLEFT, + RPLALL_GUI_MATRIX_TOPRIGHT, + RPLALL_GUI_SCROLL_TL, + RPLALL_GUI_SCROLL_TR, + + RPLALL_GUI_INSET_2, + RPLALL_GUI_MATRIX_BOTTOM, + RPLALL_GUI_SCROLL_BO, + + RPLALL_GUI_INSET_3, + RPLALL_GUI_BUTTON_RPLALL, + RPLALL_GUI_PANEL_BUTTONFLUFF_1, + RPLALL_GUI_PANEL_BUTTONFLUFF_2, + RPLALL_GUI_BUTTON_APPLY, + RPLALL_GUI_PANEL_BUTTONFLUFF_3, + RPLALL_GUI_BUTTON_CANCEL, + + RPLALL_GUI_PANEL_RESIZEFLUFF +}; + +static const NWidgetPart widgets[] = { + // title bar + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, RPLALL_GUI_CAPTION), SetDataTip(STR_TMPL_RPLALLGUI_TITLE, STR_TMPL_RPLALLGUI_TITLE), + NWidget(WWT_SHADEBOX, COLOUR_GREY), + NWidget(WWT_STICKYBOX, COLOUR_GREY), + EndContainer(), + // top matrices + NWidget(WWT_INSET, COLOUR_GREY, RPLALL_GUI_INSET_1), SetMinimalSize(100,12), SetResize(1,0), SetDataTip(STR_TMPL_RPLALLGUI_INSET_TOP, STR_TMPL_RPLALLGUI_INSET_TOP), EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_VERTICAL), + NWidget(WWT_INSET, COLOUR_GREY, RPLALL_GUI_INSET_1_1), SetMinimalSize(100,12), SetResize(1,0), SetDataTip(STR_TMPL_RPLALLGUI_INSET_TOP_1, STR_TMPL_RPLALLGUI_INSET_TOP_1), EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, RPLALL_GUI_MATRIX_TOPLEFT), SetMinimalSize(100, 16), SetFill(1, 1), SetResize(1, 1), SetScrollbar(RPLALL_GUI_SCROLL_TL),// SetDataTip(0x1, STR_REPLACE_HELP_LEFT_ARRAY), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, RPLALL_GUI_SCROLL_TL), + EndContainer(), + EndContainer(), + NWidget(NWID_VERTICAL), + NWidget(WWT_INSET, COLOUR_GREY, RPLALL_GUI_INSET_1_2), SetMinimalSize(100,12), SetResize(1,0), SetDataTip(STR_TMPL_RPLALLGUI_INSET_TOP_2, STR_TMPL_RPLALLGUI_INSET_TOP_2), EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, RPLALL_GUI_MATRIX_TOPRIGHT), SetMinimalSize(100, 16), SetFill(1, 1), SetResize(1, 1), SetScrollbar(RPLALL_GUI_SCROLL_TR),// SetDataTip(0x1, STR_REPLACE_HELP_LEFT_ARRAY), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, RPLALL_GUI_SCROLL_TR), + EndContainer(), + EndContainer(), + EndContainer(), + // bottom matrix + NWidget(WWT_INSET, COLOUR_GREY, RPLALL_GUI_INSET_2), SetMinimalSize(200,12), SetResize(1,0), SetDataTip(STR_TMPL_RPLALLGUI_INSET_BOTTOM, STR_TMPL_RPLALLGUI_INSET_BOTTOM), EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, RPLALL_GUI_MATRIX_BOTTOM), SetMinimalSize(200, 16), SetFill(1, 1), SetResize(1, 1), SetScrollbar(RPLALL_GUI_SCROLL_BO),// SetDataTip(0x1, STR_REPLACE_HELP_LEFT_ARRAY), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, RPLALL_GUI_SCROLL_BO), + EndContainer(), + // control area + NWidget(WWT_INSET, COLOUR_GREY, RPLALL_GUI_INSET_3), SetMinimalSize(200,12), SetResize(1,0), EndContainer(),// SetDataTip(STR_TMPL_MAINGUI_DEFINEDGROUPS, STR_TMPL_MAINGUI_DEFINEDGROUPS), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY, RPLALL_GUI_PANEL_BUTTONFLUFF_1), SetMinimalSize(75,12), SetResize(1,0), EndContainer(), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RPLALL_GUI_BUTTON_RPLALL), SetMinimalSize(150,12), SetResize(0,0), SetDataTip(STR_TMPL_RPLALLGUI_BUTTON_RPLALL, STR_TMPL_RPLALLGUI_BUTTON_RPLALL), + NWidget(WWT_PANEL, COLOUR_GREY, RPLALL_GUI_PANEL_BUTTONFLUFF_2), SetMinimalSize(75,12), SetResize(1,0), EndContainer(), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RPLALL_GUI_BUTTON_APPLY), SetMinimalSize(75,12), SetResize(1,0), SetDataTip(STR_TMPL_RPLALLGUI_BUTTON_APPLY, STR_TMPL_RPLALLGUI_BUTTON_APPLY), + NWidget(WWT_PANEL, COLOUR_GREY, RPLALL_GUI_PANEL_BUTTONFLUFF_3), SetMinimalSize(150,12), SetResize(0,0), EndContainer(), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, RPLALL_GUI_BUTTON_CANCEL), SetMinimalSize(75,12), SetResize(1,0), SetDataTip(STR_TMPL_RPLALLGUI_BUTTON_CANCEL, STR_TMPL_RPLALLGUI_BUTTON_CANCEL), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY, RPLALL_GUI_PANEL_RESIZEFLUFF), SetMinimalSize(100,12), SetResize(1,0), EndContainer(), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), +}; + +static WindowDesc _template_replace_replaceall_desc( + WDP_AUTO, + "template replace window", + 400, 200, + WC_TEMPLATEGUI_RPLALL, WC_NONE, + WDF_CONSTRUCTION, + widgets, lengthof(widgets) +); + +static int CDECL EngineNumberSorter(const EngineID *a, const EngineID *b) +{ + int r = Engine::Get(*a)->list_position - Engine::Get(*b)->list_position; + + return r; +} +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 r; +} + + +class TemplateReplacementReplaceAllWindow : public Window { +private: + uint16 line_height; + Scrollbar *vscroll_tl, + *vscroll_tr, + *vscroll_bo; + GUIEngineList *engines_left, + *engines_right; + short selected_left, + selected_right; + VirtTrainList *virtualTrains; + +public: + TemplateReplacementReplaceAllWindow(WindowDesc *wdesc) : Window(wdesc) + { + + this->CreateNestedTree(wdesc); + + this->vscroll_tl = this->GetScrollbar(RPLALL_GUI_SCROLL_TL); + this->vscroll_tr = this->GetScrollbar(RPLALL_GUI_SCROLL_TR); + this->vscroll_bo = this->GetScrollbar(RPLALL_GUI_SCROLL_BO); + this->vscroll_tl->SetStepSize(16); + this->vscroll_tr->SetStepSize(16); + this->vscroll_bo->SetStepSize(16); + + this->FinishInitNested(VEH_TRAIN); + + this->owner = _local_company; + + engines_left = new GUIEngineList(); + engines_right = new GUIEngineList(); + virtualTrains = new VirtTrainList(); + + this->GenerateBuyableEnginesList(); + this->GenerateIncludedTemplateList(); + + this->line_height = 16; + this->selected_left = -1; + this->selected_right = -1; + } + + ~TemplateReplacementReplaceAllWindow() + { + for ( uint i=0; ivirtualTrains->Length(); ++i ) + delete (*this->virtualTrains)[i]->vt; + SetWindowClassesDirty(WC_TEMPLATEGUI_MAIN); + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + switch ( widget ) { + case RPLALL_GUI_MATRIX_TOPLEFT: + case RPLALL_GUI_MATRIX_TOPRIGHT: + case RPLALL_GUI_MATRIX_BOTTOM: { + resize->height = 16; + size->height = 16; + break; + } + } + } + + virtual void OnPaint() + { + this->GetWidget(RPLALL_GUI_PANEL_BUTTONFLUFF_3)->colour = _company_colours[_local_company]; + + this->DrawWidgets(); + } + + virtual void OnResize() + { + NWidgetCore *nwi_tl = this->GetWidget(RPLALL_GUI_MATRIX_TOPLEFT); + this->vscroll_tl->SetCapacityFromWidget(this, RPLALL_GUI_MATRIX_TOPLEFT); + nwi_tl->widget_data = (this->vscroll_tl->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START); + + NWidgetCore *nwi_tr = this->GetWidget(RPLALL_GUI_MATRIX_TOPRIGHT); + this->vscroll_tr->SetCapacityFromWidget(this, RPLALL_GUI_MATRIX_TOPRIGHT); + nwi_tr->widget_data = (this->vscroll_tr->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START); + + NWidgetCore *nwi_bo = this->GetWidget(RPLALL_GUI_MATRIX_BOTTOM); + this->vscroll_bo->SetCapacityFromWidget(this, RPLALL_GUI_MATRIX_BOTTOM); + nwi_bo->widget_data = (this->vscroll_bo->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START); + } + + virtual void DrawWidget(const Rect &r, int widget) const + { + switch (widget) { + case RPLALL_GUI_MATRIX_TOPLEFT: { + this->DrawEngineList(r, true); + break; + } + case RPLALL_GUI_MATRIX_TOPRIGHT: { + this->DrawEngineList(r, false); + break; + } + case RPLALL_GUI_MATRIX_BOTTOM: { + this->DrawVirtualTrains(r); + break; + } + } + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + switch(widget) { + case RPLALL_GUI_MATRIX_TOPLEFT: { + uint16 newindex = (uint16)((pt.y - this->nested_array[RPLALL_GUI_MATRIX_TOPLEFT]->pos_y) / this->line_height) + this->vscroll_tl->GetPosition(); + if ( newindex >= this->engines_left->Length() || newindex==this->selected_left ) + this->selected_left = -1; + else + this->selected_left = newindex; + this->SetDirty(); + break; + } + case RPLALL_GUI_MATRIX_TOPRIGHT: { + uint16 newindex = (uint16)((pt.y - this->nested_array[RPLALL_GUI_MATRIX_TOPRIGHT]->pos_y) / this->line_height) + this->vscroll_tr->GetPosition(); + if ( newindex > this->engines_right->Length() || newindex==this->selected_right ) + this->selected_right = -1; + else + this->selected_right = newindex; + this->SetDirty(); + break; + } + case RPLALL_GUI_BUTTON_RPLALL: { + this->ReplaceAll(); + break; + } + case RPLALL_GUI_BUTTON_APPLY: { + // check if we actually did anything so far, if not, applying is forbidden + if ( this->virtualTrains->Length() == 0 ) + return; + // first delete all current templates + this->DeleteAllTemplateTrains(); + // then build a new list from the current virtual trains + for ( uint i=0; ivirtualTrains->Length(); ++i ) { + // the relevant info struct + VirtTrainInfo *vti = (*this->virtualTrains)[i]; + // setup template from contained train + Train *t = vti->vt; + TemplateVehicle *tv = TemplateVehicleFromVirtualTrain(t); + // restore template specific stuff + tv->reuse_depot_vehicles = vti->reuse_depot_vehicles; + tv->keep_remaining_vehicles = vti->keep_remaining_vehicles; + tv->refit_as_template = vti->refit_as_template; + tv->cargo_type = vti->cargo_type; + tv->cargo_subtype = vti->cargo_subtype; + // use the original_index information to repoint the relevant TemplateReplacement if existing + TemplateReplacement *tr = GetTemplateReplacementByTemplateID(vti->original_index); + if ( tr ) + tr->sel_template = tv->index; + } + // then close this window and return to parent + delete this; + break; + } + case RPLALL_GUI_BUTTON_CANCEL: { + delete this; + break; + } + } + } + + bool HasTemplateWithEngine(EngineID eid) const + { + const TemplateVehicle *tv; + FOR_ALL_TEMPLATES(tv) { + if ( tv->Prev() || tv->owner != _local_company ) continue; + for ( const TemplateVehicle *tmp=tv; tmp; tmp=tmp->GetNextUnit() ) { + if ( tmp->engine_type == eid ) + return true; + } + } + return false; + } + + void GenerateVirtualTrains() + { + this->virtualTrains->Clear(); + + TemplateVehicle *tv; + FOR_ALL_TEMPLATES(tv) { + if ( !tv->Prev() && tv->owner==this->owner ) { + // setup template train + Train *newtrain = VirtualTrainFromTemplateVehicle(tv); + VirtTrainInfo *vti = new VirtTrainInfo(newtrain); + // store template specific stuff + vti->original_index = tv->index; + vti->reuse_depot_vehicles = tv->reuse_depot_vehicles; + vti->keep_remaining_vehicles = tv->keep_remaining_vehicles; + vti->refit_as_template = tv->refit_as_template; + vti->cargo_type = tv->cargo_type; + vti->cargo_subtype = tv->cargo_subtype; + // add new info struct + *this->virtualTrains->Append() = vti; + } + } + + this->vscroll_bo->SetCount(this->virtualTrains->Length()); + } + + void DeleteAllTemplateTrains() + { + TemplateVehicle *tv, *tmp; + FOR_ALL_TEMPLATES(tv) { + tmp = tv; + if ( tmp->Prev()==0 && tmp->owner==this->owner ) + delete tmp; + } + } + + void GenerateIncludedTemplateList() + { + int num_engines = 0; + int num_wagons = 0; + + this->engines_left->Clear(); + + const Engine *e; + FOR_ALL_ENGINES_OF_TYPE(e, VEH_TRAIN) { + EngineID eid = e->index; + const RailVehicleInfo*rvi = &e->u.rail; + + if ( !HasTemplateWithEngine(eid) ) continue; + + *this->engines_left->Append() = eid; + + if (rvi->railveh_type != RAILVEH_WAGON) { + num_engines++; + } else { + num_wagons++; + } + } + this->vscroll_tl->SetCount(this->engines_left->Length()); + } + + bool VirtualTrainHasEngineID(EngineID eid) + { + + for ( uint i=0; ivirtualTrains->Length(); ++i ) { + const Train *tmp = (*this->virtualTrains)[i]->vt; + for ( ; tmp; tmp=tmp->Next() ) + if ( tmp->engine_type == eid ) + return true; + } + return false; + } + + // after 'replace all' we need to replace the currently used templates as well + void RebuildIncludedTemplateList() { + // first remove all engine ids + for ( uint i=0; iengines_left->Length(); ++i ) { + EngineID entry = (*this->engines_left)[i]; + if ( !VirtualTrainHasEngineID(entry) ) + this->engines_left->Erase(&((*this->engines_left)[i])); + } + } + + void ReplaceAll() + { + + if ( this->selected_left==-1 || this->selected_right==-1 ) + return; + + EngineID eid_orig = (*this->engines_left)[this->selected_left]; + EngineID eid_repl = (*this->engines_right)[this->selected_right]; + + if ( eid_orig == eid_repl ) + return; + + if ( this->virtualTrains->Length() == 0 ) + this->GenerateVirtualTrains(); + + for ( uint i=0; ivirtualTrains->Length(); ++i ) { + Train *tmp = (*this->virtualTrains)[i]->vt; + while ( tmp ) { + if ( tmp->engine_type == eid_orig ) { + // build a new virtual rail vehicle and test for success + Train *nt = CmdBuildVirtualRailVehicle(eid_repl); + if ( !nt ) continue; + // include the (probably) new engine into the 'included'-list + this->engines_left->Include( nt->engine_type ); + // advance the tmp pointer in the chain, otherwise it would get deleted later on + Train *to_del = tmp; + tmp = tmp->GetNextUnit(); + // first move the new virtual rail vehicle behind to_del + CommandCost move = CmdMoveRailVehicle(INVALID_TILE, DC_EXEC, nt->index|(1<<21), to_del->index, 0); + // then move to_del away from the chain and delete it + move = CmdMoveRailVehicle(INVALID_TILE, DC_EXEC, to_del->index|(1<<21), INVALID_VEHICLE, 0); + (*this->virtualTrains)[i]->vt = nt->First(); + delete to_del; + } else { + tmp = tmp->GetNextUnit(); + } + } + } + this->selected_left = -1; + // rebuild the left engines list as some engines might not be there anymore + this->RebuildIncludedTemplateList(); + this->SetDirty(); + } + + void GenerateBuyableEnginesList() + { + int num_engines = 0; + int num_wagons = 0; + + this->engines_right->Clear(); + + const Engine *e; + FOR_ALL_ENGINES_OF_TYPE(e, VEH_TRAIN) { + EngineID eid = e->index; + const RailVehicleInfo *rvi = &e->u.rail; + + if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue; + + *this->engines_right->Append() = eid; + + if (rvi->railveh_type != RAILVEH_WAGON) { + num_engines++; + } else { + num_wagons++; + } + } + + /* make engines first, and then wagons, sorted by ListPositionOfEngine() */ + EngList_Sort(this->engines_right, TrainEnginesThenWagonsSorter); + + this->vscroll_tr->SetCount(this->engines_right->Length()); + } + + void DrawEngineList(const Rect &r, bool left) const//, GUIEngineList el, Scrollbar* sb) const + { + uint16 y = r.top; + uint32 eid; + + Scrollbar *sb; + const GUIEngineList *el; + + if ( left ) { + sb = this->vscroll_tl; + el = this->engines_left; + } else { + sb = this->vscroll_tr; + el = this->engines_right; + } + + int maximum = min((int)sb->GetCapacity(), (int)el->Length()) + sb->GetPosition(); + + for ( int i=sb->GetPosition(); iselected_left == i) || (!left && this->selected_right == i) ) + GfxFillRect(r.left, y, r.right, y+this->line_height, _colour_gradient[COLOUR_GREY][3]); + + /* Draw a description string of the current engine */ + SetDParam(0, eid); + DrawString(r.left+100, r.right, y+4, STR_ENGINE_NAME, TC_BLACK); + + /* Draw the engine */ + DrawVehicleEngine( r.left, r.right, r.left+29, y+8, eid, GetEnginePalette(eid, _local_company), EIT_PURCHASE ); + + y += this->line_height; + } + } + + void DrawVirtualTrains(const Rect &r) const + { + uint16 y = r.top; + + uint16 max = min(virtualTrains->Length(), this->vscroll_bo->GetCapacity()); + + for ( uint16 i=vscroll_bo->GetPosition(); iGetPosition(); ++i ) { + /* Draw a virtual train*/ + DrawTrainImage( (*this->virtualTrains)[i]->vt, r.left+32, r.right, y, INVALID_VEHICLE, EIT_PURCHASE, 0, -1 ); + + y+= this->line_height; + } + } +}; + +void ShowTemplateReplaceAllGui() +{ + new TemplateReplacementReplaceAllWindow(&_template_replace_replaceall_desc); +} + diff --git a/src/tbtr_template_gui_replaceall.h b/src/tbtr_template_gui_replaceall.h new file mode 100644 index 0000000..c72633b --- /dev/null +++ b/src/tbtr_template_gui_replaceall.h @@ -0,0 +1,36 @@ +/* $Id: build_vehicle_gui.cpp 23792 2012-01-12 19:23:00Z yexo $ */ + +/* + * 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_replaceall.cpp Window to control the replacement of individual parts in all template trains. */ + +#ifndef TMPL_RPLALL_GUI +#define TMPL_RPLALL_GUI + +#include "stdafx.h" + +#include "window_func.h" +#include "window_gui.h" + +#include "company_func.h" +#include "core/math_func.hpp" +#include "engine_base.h" +#include "engine_func.h" +#include "engine_gui.h" +#include "strings_func.h" +#include "table/strings.h" +#include "train.h" +#include "vehicle_base.h" +#include "vehicle_func.h" + +#include "tbtr_template_vehicle.h" +#include "tbtr_template_vehicle_func.h" + +void ShowTemplateReplaceAllGui(); + +#endif diff --git a/src/tbtr_template_vehicle.cpp b/src/tbtr_template_vehicle.cpp new file mode 100644 index 0000000..44ee4f8 --- /dev/null +++ b/src/tbtr_template_vehicle.cpp @@ -0,0 +1,174 @@ +/* $Id: build_vehicle_gui.cpp 23792 2012-01-12 19:23:00Z yexo $ */ + +/* + * 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 The class definition for a template train. */ + +#include "stdafx.h" + +#include "core/pool_func.hpp" + +#include "tbtr_template_vehicle.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 0; +} + +TemplateReplacement* GetTemplateReplacementByTemplateID(TemplateID tid) { + TemplateReplacement *tr; + FOR_ALL_TEMPLATE_REPLACEMENTS(tr) { + if ( tr->Template() == tid ) + return tr; + } + return 0; +} + +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; 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 0000000..37c5e91 --- /dev/null +++ b/src/tbtr_template_vehicle.h @@ -0,0 +1,198 @@ +/* $Id: build_vehicle_gui.cpp 23792 2012-01-12 19:23:00Z yexo $ */ + +/* + * 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 The class definitions for template trains, template replacements and virtual trains. */ + +#ifndef TEMPLATE_VEH_H +#define TEMPLATE_VEH_H + +#include "articulated_vehicles.h" +#include "company_func.h" +#include "engine_base.h" +#include "engine_func.h" +#include "engine_type.h" +#include "newgrf_callbacks.h" +#include "newgrf_engine.h" +#include "newgrf_spritegroup.h" +#include "sortlist_type.h" +#include "vehicle_base.h" +#include "vehicle_func.h" +#include "vehicle_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=0; + previous=0; + 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); } + + 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; + //int GetDisplayImageWidth(Point *offset = NULL) 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 0000000..a705923 --- /dev/null +++ b/src/tbtr_template_vehicle_func.cpp @@ -0,0 +1,789 @@ +/* $Id: build_vehicle_gui.cpp 23792 2012-01-12 19:23:00Z yexo $ */ + +/* + * 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 Various setup and utility functions around template trains. */ + +#include "stdafx.h" + +#include "autoreplace_func.h" +#include "command_func.h" +#include "train.h" + +#include "tbtr_template_vehicle_func.h" + +Vehicle *vhead, *vtmp; +static const uint MAX_ARTICULATED_PARTS = 100; + +/* + * forward declaration, from train_cmd.cpp + */ +CommandCost CmdSellRailWagon(DoCommandFlag, Vehicle*, uint16, uint32); + +/** + * Fill the given GUITemplateList with all template vehicles defined for the given owner, which are the head of a chain. + */ +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()); +} + +/** + * Calculate the total Money-value of a given template vehicle chain. + * @param tv: The given template vehicle is treated as the head of a chain. The value is only summed up for this vehicle and all following vehicles in the chain. + */ +Money CalculateOverallTemplateCost(const TemplateVehicle *tv) +{ + Money val = 0; + + for (; tv; tv = tv->Next()) + val += (Engine::Get(tv->engine_type))->GetCost(); + return val; +} + +/** + * Draw a given template vehicle by its stored sprite id and a given position. + */ +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 relevant values from a (virtual) train onto a template vehicle. + * @param virt: The train from which to copy the values. + * @param tmpl_veh: The template for which the values are to be setthat receives the values + * @param prev: The previous template vehicle in a chain. Used to chain the current template vehicle to it's precedessor. + */ +inline void SetupTemplateVehicleFromVirtual(Train* virt, TemplateVehicle* tmpl_veh, TemplateVehicle* prev) +{ + if (prev) { + prev->SetNext(tmpl_veh); + tmpl_veh->SetPrev(prev); + tmpl_veh->SetFirst(prev->First()); + } + tmpl_veh->railtype = virt->railtype; + tmpl_veh->owner = virt->owner; + tmpl_veh->value = virt->value; + + // set the subtype but also clear the virtual flag while doing it + tmpl_veh->subtype = virt->subtype & ~(1 << GVSF_VIRTUAL); + // set the cargo type and capacity + tmpl_veh->cargo_type = virt->cargo_type; + tmpl_veh->cargo_subtype = virt->cargo_subtype; + tmpl_veh->cargo_cap = virt->cargo_cap; + + const GroundVehicleCache *gcache = virt->GetGroundVehicleCache(); + tmpl_veh->max_speed = virt->GetDisplayMaxSpeed(); + tmpl_veh->power = gcache->cached_power; + tmpl_veh->weight = gcache->cached_weight; + tmpl_veh->max_te = gcache->cached_max_te / 1000; + + tmpl_veh->spritenum = virt->spritenum; + VehicleSpriteSeq seq; + virt->GetImage(DIR_W, EIT_PURCHASE, &seq); + tmpl_veh->cur_image = seq.seq[0].sprite; + Point *p = new Point(); + tmpl_veh->image_width = virt->GetDisplayImageWidth(p); +} + +/* + * Create a Virtual Train as a clone from a Train object. + */ +Train* CloneVirtualTrainFromTrain(const Train *clicked) +{ + if ( !clicked ) return 0; + CommandCost c; + Train *tmp, *head, *tail; + + head = CmdBuildVirtualRailVehicle(clicked->engine_type); + if ( !head ) return 0; + + tail = head; + clicked = clicked->GetNextUnit(); + while ( clicked ) { + tmp = CmdBuildVirtualRailVehicle(clicked->engine_type); + if ( tmp ) { + tmp->cargo_type = clicked->cargo_type; + tmp->cargo_subtype = clicked->cargo_subtype; + CmdMoveRailVehicle(0, DC_EXEC, (1<<21) | tmp->index, tail->index, 0); + tail = tmp; + } + clicked = clicked->GetNextUnit(); + } + return head; +} + +/* + * Create a Template Train as a clone from a Train object. + */ +TemplateVehicle* CloneTemplateVehicleFromTrain(const Train *t) +{ + Train *clicked = Train::Get(t->index); + if ( !clicked ) + return 0; + + Train *init_clicked = clicked; + + int len = CountVehiclesInChain(clicked); + if ( !TemplateVehicle::CanAllocateItem(len) ) + return 0; + + TemplateVehicle *tmp, *prev=0; + for ( ; clicked; clicked=clicked->Next() ) { + tmp = new TemplateVehicle(clicked->engine_type); + SetupTemplateVehicleFromVirtual(clicked, tmp, prev); + prev = tmp; + } + + tmp->First()->SetRealLength(CeilDiv(init_clicked->gcache.cached_total_length * 10, TILE_SIZE)); + return tmp->First(); +} + +/* + * Create a new Template Train as a clone from a Virtual Train. + * The new template will contain all necessary details that can be extracted + * from the virtual train. + */ +TemplateVehicle* TemplateVehicleFromVirtualTrain(Train *virt) +{ + if ( !virt ) + return 0; + + Train *init_virt = virt; + + int len = CountVehiclesInChain(virt); + if ( !TemplateVehicle::CanAllocateItem(len) ) + return 0; + + TemplateVehicle *tmp, *prev=0; + for ( ; virt; virt=virt->Next() ) { + tmp = new TemplateVehicle(virt->engine_type); + SetupTemplateVehicleFromVirtual(virt, tmp, prev); + prev = tmp; + } + + tmp->First()->SetRealLength(CeilDiv(init_virt->gcache.cached_total_length * 10, TILE_SIZE)); + return tmp->First(); +} + +/* + * Create a Virtual Train corresponding to a given Template Vehicle. + */ +Train* VirtualTrainFromTemplateVehicle(TemplateVehicle *tv) +{ + if ( !tv ) return 0; + CommandCost c; + Train *tmp, *head, *tail; + + head = CmdBuildVirtualRailVehicle(tv->engine_type); + if ( !head ) return 0; + + 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(); + } + return head; +} + +/* + * Return the last part of a Template Vehicle chain + * This will return really the last part, i.e. even the last part of an + * articulated vehicle, if chain ends with such an articulated vehicle. + */ +inline TemplateVehicle* Last(TemplateVehicle *chain) { + if ( !chain ) return 0; + while ( chain->Next() ) chain = chain->Next(); + return chain; +} + +/* + * Return the last part of a Train + * This will return really the last part, i.e. even the last part of an + * articulated vehicle, if chain ends with such an articulated vehicle. + */ +inline Train* Last(Train *chain) { + if ( !chain ) return 0; + while ( chain->GetNextUnit() ) chain = chain->GetNextUnit(); + return chain; +} + +TemplateVehicle *DeleteTemplateVehicle(TemplateVehicle *todel) +{ + if ( !todel ) + return 0; + TemplateVehicle *cur = todel; + delete todel; + return cur; +} + +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 +/* + * Find the Template Vehicle for a given vehicle group. + * The template is looked up by finding the template replacement that is + * currently set for the given group, if any is set. + * @param: gid the ID of the group for which to find the currently used template + * @return pointer to the corresponding template vechicle, NULL if no + * template replacement is defined for 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 0; +} + +/** + * Check, if a given template consist 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; +} + +/* + * Check, whether a given Train object contains another Train + * The maybe-contained train is not check for complete inclusion, it is rather + * treated as if it was a one-vehicle chain + */ +bool ChainContainsVehicle(Train *chain, Train *mem) { + for (; chain; chain=chain->Next()) + if ( chain == mem ) + return true; + return false; +} + +/* + * Checks, if any vehicle in a given Train contains has a given EngineID + */ +Train* ChainContainsEngine(Train* chain, EngineID eid) { + for (; chain; chain=chain->GetNextUnit()) + if (chain->engine_type == eid) + return chain; + return 0; +} + +/* + * Check, if any train in a given Depot contains a given EngineID + * @param tile: the tile of the depot + * @param eid: the EngineID to look up + * @param not_in this Train will be ignored during the check + */ +Train* DepotContainsEngine(TileIndex tile, EngineID eid, Train *not_in=0) { + 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==0 || ChainContainsVehicle(not_in, t)==0)) + return t; + } + return 0; +} + +/* + * Copy some details of one Train onto another + */ +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; +} + +/* + * Reset a train's group and name + */ +void NeutralizeStatus(Train *t) { + DoCommand(t->tile, DEFAULT_GROUP, t->index, DC_EXEC, CMD_ADD_VEHICLE_GROUP); + + t->name = 0; +} + +/* + * Check, if a given train fully matches a template train's engine ids + * Both chains must have the exact same number of members all of which have the + * same engine ids. + */ +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; +} + +/* + * Check, if a given train completely matches a template's refit settings + * This check will still succeed if any of the two given chains has more + * vehicles than the other. + */ +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; +} + +/* + * Break up the remainders of a chain after template replacement + * All primary vehicles in the given chain are moved to form a new chain on the + * same tile (presumably inside a depot). All following wagons after a train + * engine are kept with the engine in order to create only as many new chains + * as necessary. + */ +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(); + } +} + +/* + * Count and return the number of engines in a given train + */ +short CountEnginesInChain(Train *t) +{ + short count = 0; + for ( ; t; t=t->GetNextUnit() ) + if ( HasBit(t->subtype, GVSF_ENGINE) ) + count++; + return count; +} + +/* + * Count and return the number of vehicles with a spcific engine id in a train + */ +int countOccurrencesInTrain(Train *t, EngineID eid) { + int count = 0; + Train *tmp = t; + for ( ; tmp; tmp=tmp->GetNextUnit() ) + if ( tmp->engine_type == eid ) + count++; + return count; +} + +/* + * Count the number of occurrences of a specific engine id in a depot + * @param tile: the tile of the depot + * @param eid: the engine id to count + * @param not_in: ignore this train for the count + */ +int countOccurrencesInDepot(TileIndex tile, EngineID eid, Train *not_in=0) { + 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)==0)) + count++; + } + return count; +} + +// basically does the same steps as CmdTemplateReplaceTrain 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; 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; jsecond; j++ ) { + estimate.AddCost(DoCommand(tile, it->first, 0, DC_NONE, CMD_BUILD_VEHICLE)); + } + } + + return estimate; +} + +/* + * Copy the status of a single template wagon onto a Train wagon + * Both inputs are treated as singular vehicles. + */ +void CopyWagonStatus(TemplateVehicle *from, Train *to) { + to->cargo_type = from->cargo_type; + to->cargo_subtype = from->cargo_subtype; +} + +/* + * Count and return the number of Trains that currently need template replacement + */ +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; +} + +/* + * Copy the refit status from a complete Template train onto a complete Train + */ +static void RefitTrainFromTemplate(Train *t, TemplateVehicle *tv) +{ + while ( t && tv ) { + // refit t as tv + DoCommand(t->tile, t->index, tv->cargo_type | tv->cargo_subtype << 8 | 1 << 16 , DC_EXEC, CMD_REFIT_VEHICLE); + + // next + t = t->GetNextUnit(); + tv = tv->GetNextUnit(); + } +} + +/* + * Return the total cost of all parts of a Template train + * + * This function is currently too simplistic. It should take into account what vehicles can be reused from either + * the incoming train or from the vehicles in the depot. Though both these factors also depend on the settings of + * the currently used template. + * What this function currently does is to assume that ALL vehicles in the given template train must be bought. + * This seems to be safe minimum requirement but it will be extended in the future. + */ +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 + */ +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 = std::min(remainingAmount, curCap); + // move (parts of) the old vehicle's cargo onto the current vehicle of the new chain + if (moveAmount > 0) + { + old_veh->cargo.Shift(moveAmount, &tmp->cargo); + remainingAmount -= moveAmount; + } + } + } + + // 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(ConsistChangeFlags::CCF_LOADUNLOAD); +} + +// TODO this contains the exact content of the old Cmd function above +CommandCost cmd_helper_func(Train *incoming, bool stayInDepot, DoCommandFlag flags) { + Train *new_chain=0, + *remainder_chain=0, + *tmp_chain=0; + + CommandCost buy(EXPENSES_NEW_VEHICLES); + CommandCost move_cost(EXPENSES_NEW_VEHICLES); + CommandCost tmp_result(EXPENSES_NEW_VEHICLES); + + /* first some tests on necessity and sanity */ + TileIndex tile = incoming->tile; + TemplateVehicle *tv = GetTemplateVehicleByGroupID(incoming->group_id); + if ( !tv ) + return buy; + EngineID eid = tv->engine_type; + 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; getc=getc->GetNextUnit() ) + if ( getc->cargo_type != CT_INVALID ) { + store_refit_ct = getc->cargo_type; + break; + } + } + 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; + } + } + + /* check overall cost of applying this template replacement */ + CommandCost testbuy_cost = TestBuyAllTemplateVehiclesInChain(tv, tile); + /* Under flags DC_NONE we just want to know the overall cost of the replacement and whether the current company can + * afford it. */ + if (flags != DC_EXEC) + return testbuy_cost; + /* Under flags DC_EXEC we still abort only if the current company can NOT affort the replacement. */ + else if (!testbuy_cost.Succeeded()) + return testbuy_cost; + + + /* define replacement behaviour */ + 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(incoming, eid)) && 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 ) { + DoCommand(new_chain->tile, new_chain->index, store_refit_ct | store_refit_csubt << 8 | 1 << 16 , DC_EXEC, CMD_REFIT_VEHICLE); + } + + } + + /// 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(remainder_chain, cur_tmpl->engine_type)) && 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(DoCommand(tile, tmp_chain->index, last_veh->index, flags, CMD_MOVE_RAIL_VEHICLE)); + } + if ( need_refit && flags == DC_EXEC ) { + if ( use_refit ) { + DoCommand(tmp_chain->tile, tmp_chain->index, cur_tmpl->cargo_type | cur_tmpl->cargo_subtype << 8 | 1 << 16 , DC_EXEC, CMD_REFIT_VEHICLE); + } else { + DoCommand(tmp_chain->tile, tmp_chain->index, store_refit_ct | store_refit_csubt << 8 | 1 << 16 , DC_EXEC, CMD_REFIT_VEHICLE); + } + } + 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) ) { + RefitTrainFromTemplate(new_chain, tv); + } + + 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)); + } + return buy; +} +CommandCost CmdTemplateReplaceTrain(TileIndex ti, DoCommandFlag flags, uint32 p1, uint32 p2, char const* msg) { + VehicleID id_inc = GB(p1, 0, 20); + Train* incoming = Train::GetIfValid(id_inc); + // TODO check if null + + bool stayInDepot = p2; + + return cmd_helper_func(incoming, stayInDepot, flags); +} + diff --git a/src/tbtr_template_vehicle_func.h b/src/tbtr_template_vehicle_func.h new file mode 100644 index 0000000..7d79519 --- /dev/null +++ b/src/tbtr_template_vehicle_func.h @@ -0,0 +1,71 @@ +/* $Id: build_vehicle_gui.cpp 23792 2012-01-12 19:23:00Z yexo $ */ + +/* + * 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 Various setup and utility functions around template trains. */ + +#ifndef TEMPLATE_VEHICLE_FUNC_H +#define TEMPLATE_VEHICLE_FUNC_H + +#include "stdafx.h" + +#include "window_gui.h" + +#include "tbtr_template_vehicle.h" + +//void DrawTemplateVehicle(TemplateVehicle*, int, const Rect&); +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*); + +Train* VirtualTrainFromTemplateVehicle(TemplateVehicle*); + +inline TemplateVehicle* Last(TemplateVehicle*); + +TemplateVehicle *DeleteTemplateVehicle(TemplateVehicle*); + +Train* DeleteVirtualTrain(Train*, Train *); + +CommandCost CmdTemplateReplaceTrain(TileIndex, DoCommandFlag, uint32, uint32, char const*); + +TemplateVehicle* GetTemplateVehicleByGroupID(GroupID); +bool ChainContainsVehicle(Train*, Train*); +Train* ChainContainsEngine(Train*, EngineID); +Train* DepotContainsEngine(TileIndex, EngineID, Train*); + +int NumTrainsNeedTemplateReplacement(GroupID, TemplateVehicle*); + +CommandCost TestBuyAllTemplateVehiclesInChain(Train*); +CommandCost CalculateTemplateReplacementCost(Train*); + +short CountEnginesInChain(Train*); + +bool TemplateVehicleContainsEngineOfRailtype(const TemplateVehicle*, RailType); + +Train* CloneVirtualTrainFromTrain(const Train *); +TemplateVehicle* CloneTemplateVehicleFromTrain(const Train *); + +void TransferCargoForTrain(Train*, Train*); + +#endif diff --git a/src/train.h b/src/train.h index 82e33b6..6c31642 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,15 @@ 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 +346,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 5502532..69ef2e2 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -41,6 +41,8 @@ #include "safeguards.h" +#include "engine_func.h" + static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool force_res, bool *got_reservation, bool mark_stuck); static bool TrainCheckIfLineEnds(Train *v, bool reverse = true); bool TrainController(Train *v, Vehicle *nomove, bool reverse = true); // Also used in vehicle_sl.cpp. @@ -259,7 +261,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); @@ -1169,6 +1171,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 @@ -1233,10 +1236,14 @@ 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. */ @@ -1345,8 +1352,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); @@ -1429,8 +1439,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; @@ -3752,6 +3765,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) { @@ -4046,3 +4069,156 @@ 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; + } + //printf(" refwid:%d gdiw.cachedvehlen(%d):%d ", reference_width, this->engine_type, this->gcache.cached_veh_length); + 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->random_bits = VehicleRandomBits(); + + v->group_id = DEFAULT_GROUP; + + AddArticulatedParts(v); + + _new_vehicle_id = v->index; + + // from revision r22xxx + // VehicleMove(v, false); + // new + v->UpdateViewport(true, false); + + v->First()->ConsistChanged(ConsistChangeFlags::CCF_ARRANGE); + //UpdateTrainGroupID(v->First()); + + CheckConsistencyOfArticulatedVehicle(v); + + /* The GVSF_VIRTUAL flag is used to prevent depot-tile sanity checks */ + SetBit(v->subtype, GVSF_VIRTUAL); + +// GroupStatistics::CountVehicle( v, -1 ); + + 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 0; + 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 0; + 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->random_bits = VehicleRandomBits(); + + v->group_id = DEFAULT_GROUP; + + v->SetFrontEngine(); + v->SetEngine(); + + // from revision r22xxx +// VehicleMove(v, false); + // new + v->UpdateViewport(true, false); + + if (rvi->railveh_type == RAILVEH_MULTIHEAD) { + AddRearEngineToMultiheadedTrain(v); + } else { + AddArticulatedParts(v); + } + + v->ConsistChanged(ConsistChangeFlags::CCF_ARRANGE); + //UpdateTrainGroupID(v); + + CheckConsistencyOfArticulatedVehicle(v); + + SetBit(v->subtype, GVSF_VIRTUAL); + +// GroupStatistics::CountVehicle( v, -1 ); + + return v; +} diff --git a/src/vehicle.cpp b/src/vehicle.cpp index b686461..aad32ce 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -57,6 +57,8 @@ #include "safeguards.h" +#include "tbtr_template_vehicle_func.h" + #define GEN_HASH(x, y) ((GB((y), 6 + ZOOM_LVL_SHIFT, 6) << 6) + GB((x), 7 + ZOOM_LVL_SHIFT, 6)) VehicleID _new_vehicle_id; @@ -667,6 +669,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(); @@ -870,6 +879,25 @@ Vehicle::~Vehicle() */ void VehicleEnteredDepotThisTick(Vehicle *v) { + /* Template Replacement Setup stuff */ + bool stayInDepot = v->current_order.GetDepotActionType(); + TemplateReplacement *tr = GetTemplateReplacementByGroupID(v->group_id); + if ( tr ) { + if ( stayInDepot ) _vehicles_to_templatereplace[(Train*)v] = true; + else _vehicles_to_templatereplace[(Train*)v] = false; + } + /* + * Always schedule trains for auto replacement, even if they're already + * scheduled for template replacement. This ensures that if a vehicle is + * too old and needs to be auto-replaced, but template replacement fails, + * it will still try to auto replace the vehicle. + * Later, we first try to template replace a vehicle and if that succeeds, + * we de-schedule its auto-replacement. Doing the template replacement first + * and then auto-replacement ensures that we don't enter a state where the + * vehicle does not have a proper template vehicle assigned, thus skipping + * its template replacement entirely and auto-replacing it instead, even + * when a template replacement was originally intended + */ /* Vehicle should stop in the depot if it was in 'stopping' state */ _vehicles_to_autoreplace[v] = !(v->vehstatus & VS_STOPPED); @@ -878,6 +906,7 @@ void VehicleEnteredDepotThisTick(Vehicle *v) * the path out of the depot before we might autoreplace it to a different * engine. The new engine would not own the reserved path we store that we * stopped the vehicle, so autoreplace can start it again */ + v->vehstatus |= VS_STOPPED; } @@ -919,6 +948,7 @@ static void RunVehicleDayProc() void CallVehicleTicks() { _vehicles_to_autoreplace.Clear(); + _vehicles_to_templatereplace.Clear(); RunVehicleDayProc(); @@ -995,6 +1025,28 @@ void CallVehicleTicks() } } + /* 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; + + tmpl_cur_company.Change(t->owner); + + bool stayInDepot = it->second; + + it->first->vehstatus |= VS_STOPPED; + CommandCost cost = DoCommand(0, t->index, stayInDepot, DC_EXEC, CMD_TEMPLATE_REPLACE_TRAIN); + /* if it wasn't for free, it succeeded, so don't auto-replace it */ + if(cost.GetCost() != 0) { + _vehicles_to_autoreplace.Erase(t); + } + /* Redraw main gui for changed statistics */ + SetWindowClassesDirty(WC_TEMPLATEGUI_MAIN); + } + tmpl_cur_company.Restore(); + + /* 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; @@ -1039,7 +1091,6 @@ void CallVehicleTicks() SetDParam(1, error_message); AddVehicleAdviceNewsItem(message, v->index); } - cur_company.Restore(); } diff --git a/src/vehicle_base.h b/src/vehicle_base.h index f2a0207..b128fab 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -27,6 +27,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. @@ -116,6 +118,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. */ @@ -572,6 +575,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 9670fa0..7d1d46c 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -422,6 +422,7 @@ static CommandCost RefitVehicle(Vehicle *v, bool only_this, uint8 num_vehicles, * @param p2 various bitstuffed elements * - p2 = (bit 0-4) - New cargo type to refit to. * - p2 = (bit 6) - Automatic refitting. + * - p2 = (bit 5) - Is a virtual train (used by template replacement to allow refitting without stopped-in-depot checks) * - 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(). * - p2 = (bit 16-23) - Number of vehicles to refit (not counting articulated parts). Zero means all vehicles. @@ -444,16 +445,22 @@ 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 - !front->IsStoppedInDepot()) { // refit inside depots + !front->IsStoppedInDepot() && // refit inside depots + !is_virtual_train) { // if it's a virtual train, don't check for depot return_cmd_error(STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT + front->type); } @@ -499,7 +506,9 @@ 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(); diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 8ea8cda..18b3899 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -37,11 +37,11 @@ #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" - Sorting _sorting; static GUIVehicleList::SortFunction VehicleNumberSorter; @@ -168,6 +168,7 @@ DropDownList *BaseVehicleListWindow::BuildActionDropdownList(bool show_autorepla { DropDownList *list = new DropDownList(); + *list->Append() = new DropDownListStringItem(STR_TMPL_TEMPLATE_REPLACEMENT, ADI_TEMPLATE_REPLACE, false); if (show_autoreplace) *list->Append() = new DropDownListStringItem(STR_VEHICLE_LIST_REPLACE_VEHICLES, ADI_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 +401,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 +583,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 +952,9 @@ 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 +1035,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; } @@ -1663,6 +1666,9 @@ public: case ADI_REPLACE: // Replace window ShowReplaceGroupVehicleWindow(ALL_GROUP, this->vli.vtype); break; + case ADI_TEMPLATE_REPLACE: + 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 9297542..b0ab52a 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 5755c7f..839f1a2 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, diff --git a/src/vehiclelist.cpp b/src/vehiclelist.cpp index f1f5d04..1880463 100644 --- a/src/vehiclelist.cpp +++ b/src/vehiclelist.cpp @@ -149,7 +149,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; } @@ -160,7 +160,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 a1bb2c8..2b3fe6e 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/widgets/build_vehicle_widget.h b/src/widgets/build_vehicle_widget.h index ae54858..110a995 100644 --- a/src/widgets/build_vehicle_widget.h +++ b/src/widgets/build_vehicle_widget.h @@ -26,6 +26,7 @@ enum BuildVehicleWidgets { WID_BV_SHOW_HIDE, ///< Button to hide or show the selected engine. WID_BV_BUILD_SEL, ///< Build button. WID_BV_RENAME, ///< Rename button. + BUILD_VEHICLE_WIDGET_BUILD, /// TODO: own }; #endif /* WIDGETS_BUILD_VEHICLE_WIDGET_H */ diff --git a/src/window_type.h b/src/window_type.h index 809e81d..14d1059 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. };