diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj
index 351989dd4a..93f7dbbabc 100644
--- a/projects/openttd_vs100.vcxproj
+++ b/projects/openttd_vs100.vcxproj
@@ -291,6 +291,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -897,6 +907,8 @@
+
+
diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters
index be295c10b8..e2a0b91953 100644
--- a/projects/openttd_vs100.vcxproj.filters
+++ b/projects/openttd_vs100.vcxproj.filters
@@ -102,6 +102,36 @@
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
Source Files
@@ -1920,6 +1950,12 @@
Save/Load handlers
+
+ Save/Load handlers
+
+
+ Save/Load handlers
+
Tables
diff --git a/projects/openttd_vs140.vcxproj b/projects/openttd_vs140.vcxproj
index 7698bc0497..6bf52363bb 100644
--- a/projects/openttd_vs140.vcxproj
+++ b/projects/openttd_vs140.vcxproj
@@ -308,6 +308,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -914,6 +924,8 @@
+
+
diff --git a/projects/openttd_vs140.vcxproj.filters b/projects/openttd_vs140.vcxproj.filters
index be295c10b8..e2a0b91953 100644
--- a/projects/openttd_vs140.vcxproj.filters
+++ b/projects/openttd_vs140.vcxproj.filters
@@ -102,6 +102,36 @@
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
Source Files
@@ -1920,6 +1950,12 @@
Save/Load handlers
+
+ Save/Load handlers
+
+
+ Save/Load handlers
+
Tables
diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj
index 9998f812c2..a0b53570c6 100644
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -434,6 +434,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2882,6 +2922,14 @@
RelativePath=".\..\src\saveload\extended_ver_sl.cpp"
>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2879,6 +2919,14 @@
RelativePath=".\..\src\saveload\extended_ver_sl.cpp"
>
+
+
+
+
{
*/
inline void ClearFreeWagon() { ClrBit(this->subtype, GVSF_FREE_WAGON); }
+ /**
+ * Set a vehicle as a virtual vehicle.
+ */
+ inline void SetVirtual() { SetBit(this->subtype, GVSF_VIRTUAL); }
+
+ /**
+ * Clear a vehicle from being a virtual vehicle.
+ */
+ inline void ClearVirtual() { ClrBit(this->subtype, GVSF_VIRTUAL); }
+
/**
* Set a vehicle as a multiheaded engine.
*/
@@ -332,6 +342,12 @@ struct GroundVehicle : public SpecializedVehicle {
*/
inline bool IsMultiheaded() const { return HasBit(this->subtype, GVSF_MULTIHEADED); }
+ /**
+ * Tell if we are dealing with a virtual vehicle (used for templates).
+ * @return True if the vehicle is a virtual vehicle.
+ */
+ inline bool IsVirtual() const { return HasBit(this->subtype, GVSF_VIRTUAL); }
+
/**
* Tell if we are dealing with the rear end of a multiheaded engine.
* @return True if the engine is the rear part of a dualheaded engine.
diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp
index 12cce41f74..55873fb8d4 100644
--- a/src/group_cmd.cpp
+++ b/src/group_cmd.cpp
@@ -21,6 +21,7 @@
#include "company_func.h"
#include "core/pool_func.hpp"
#include "order_backup.h"
+#include "tbtr_template_vehicle.h"
#include "table/strings.h"
@@ -137,6 +138,9 @@ void GroupStatistics::Clear()
*/
/* static */ void GroupStatistics::CountVehicle(const Vehicle *v, int delta)
{
+ /* make virtual trains group-neutral */
+ if ( HasBit(v->subtype, GVSF_VIRTUAL) ) return;
+
assert(delta == 1 || delta == -1);
GroupStatistics &stats_all = GroupStatistics::GetAllGroup(v);
@@ -341,6 +345,9 @@ CommandCost CmdDeleteGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
VehicleType vt = g->vehicle_type;
+ /* Delete all template replacements using the just deleted group */
+ deleteIllegalTemplateReplacements(g->index);
+
/* Delete the Replace Vehicle Windows */
DeleteWindowById(WC_REPLACE_VEHICLE, g->vehicle_type);
delete g;
diff --git a/src/group_gui.cpp b/src/group_gui.cpp
index ebc77efbf1..5f08d595e4 100644
--- a/src/group_gui.cpp
+++ b/src/group_gui.cpp
@@ -25,6 +25,8 @@
#include "vehicle_gui_base.h"
#include "core/geometry_func.hpp"
#include "company_base.h"
+#include "tbtr_template_gui_main.h"
+
#include "widgets/group_widget.h"
#include "core/smallvec_type.hpp"
@@ -439,7 +441,7 @@ public:
break;
case WID_GL_MANAGE_VEHICLES_DROPDOWN: {
- Dimension d = this->GetActionDropdownSize(true, true);
+ Dimension d = this->GetActionDropdownSize(true, true, this->vli.vtype == VEH_TRAIN);
d.height += padding.height;
d.width += padding.width;
*size = maxdim(*size, d);
@@ -751,6 +753,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;
}
@@ -789,7 +792,7 @@ public:
break;
case WID_GL_MANAGE_VEHICLES_DROPDOWN: {
- DropDownList *list = this->BuildActionDropdownList(true, Group::IsValidID(this->vli.index));
+ DropDownList *list = this->BuildActionDropdownList(true, Group::IsValidID(this->vli.index), this->vli.vtype == VEH_TRAIN);
ShowDropDownList(this, list, 0, WID_GL_MANAGE_VEHICLES_DROPDOWN);
break;
}
@@ -898,6 +901,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;
}
@@ -918,6 +922,11 @@ public:
assert(this->vehicles.Length() != 0);
switch (index) {
+ case ADI_TEMPLATE_REPLACE: // TemplateReplace Window
+ if (vli.vtype == VEH_TRAIN) {
+ ShowTemplateReplaceWindow(this->unitnumber_digits, this->resize.step_height);
+ }
+ break;
case ADI_REPLACE: // Replace window
ShowReplaceGroupVehicleWindow(this->vli.index, this->vli.vtype);
break;
diff --git a/src/lang/english.txt b/src/lang/english.txt
index 274b93325c..91fd792553 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -5582,3 +5582,62 @@ STR_ZONING_STA_CATCH_OPEN :Station catchme
STR_ZONING_BUL_UNSER :Unserved buildings
STR_ZONING_IND_UNSER :Unserved industries
STR_ZONING_TRACERESTRICT :Restricted signals
+
+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/lang/french.txt b/src/lang/french.txt
index 8ecc2db4ed..dc97e8d3dd 100644
--- a/src/lang/french.txt
+++ b/src/lang/french.txt
@@ -167,7 +167,7 @@ STR_ABBREV_ALL :{TINY_FONT}ALL
# 'Mode' of transport for cargoes
STR_PASSENGERS :{COMMA}{NBSP}passager{P "" s}
-STR_BAGS :{COMMA}{NBSP}sac{P "" s}
+STR_BAGS :{COMMA}{NBSP}sac{P 0 "" s}
STR_TONS :{COMMA}{NBSP}tonne{P "" s}
STR_LITERS :{COMMA}{NBSP}litre{P "" s}
STR_ITEMS :{COMMA}{NBSP}unité{P "" s}
@@ -815,8 +815,8 @@ STR_PRESIDENT_NAME_MANAGER :{BLACK}{PRESIDE
STR_NEWS_NEW_TOWN :{BLACK}{BIG_FONT}{STRING} a sponsorisé la construction de la nouvelle ville {TOWN}{NBSP}!
-STR_NEWS_INDUSTRY_CONSTRUCTION :{BIG_FONT}{BLACK}Un{G "" "" e} nouv{G eau el elle} {STRING} en construction près de {TOWN}{NBSP}!
-STR_NEWS_INDUSTRY_PLANTED :{BIG_FONT}{BLACK}Un{G "" "" e} nouv{G eau el elle} {STRING} s'implante près de {TOWN}{NBSP}!
+STR_NEWS_INDUSTRY_CONSTRUCTION :{BIG_FONT}{BLACK}Un{G 0 "" "" e} nouv{G 0 eau el elle} {STRING} en construction près de {TOWN}{NBSP}!
+STR_NEWS_INDUSTRY_PLANTED :{BIG_FONT}{BLACK}Un{G 0 "" "" e} nouv{G 0 eau el elle} {STRING} s'implante près de {TOWN}{NBSP}!
STR_NEWS_INDUSTRY_CLOSURE_GENERAL :{BIG_FONT}{BLACK}{STRING} annonce une fermeture imminente{NBSP}!
STR_NEWS_INDUSTRY_CLOSURE_SUPPLY_PROBLEMS :{BIG_FONT}{BLACK}Des problèmes d'approvisionnement obligent {STRING} à fermer bientôt{NBSP}!
@@ -858,9 +858,9 @@ STR_NEWS_AIRCRAFT_DEST_TOO_FAR :{WHITE}{VEHICLE
STR_NEWS_ORDER_REFIT_FAILED :{WHITE}L'échec du réaménagement a stoppé {VEHICLE}
STR_NEWS_VEHICLE_AUTORENEW_FAILED :{WHITE}Le renouvellement automatique de {VEHICLE} a échoué{}{STRING}
-STR_NEWS_NEW_VEHICLE_NOW_AVAILABLE :{BIG_FONT}{BLACK}Nouv{G eau el elle} {STRING} disponible{NBSP}!
+STR_NEWS_NEW_VEHICLE_NOW_AVAILABLE :{BIG_FONT}{BLACK}Nouv{G 0 eau el elle} {STRING} disponible{NBSP}!
STR_NEWS_NEW_VEHICLE_TYPE :{BIG_FONT}{BLACK}{ENGINE}
-STR_NEWS_NEW_VEHICLE_NOW_AVAILABLE_WITH_TYPE :{BLACK}Nouv{G eau el elle} {STRING} disponible{NBSP}! - {ENGINE}
+STR_NEWS_NEW_VEHICLE_NOW_AVAILABLE_WITH_TYPE :{BLACK}Nouv{G 0 eau el elle} {STRING} disponible{NBSP}! - {ENGINE}
STR_NEWS_STATION_NO_LONGER_ACCEPTS_CARGO :{WHITE}{STATION} n'accepte plus {STRING}
STR_NEWS_STATION_NO_LONGER_ACCEPTS_CARGO_OR_CARGO :{WHITE}{STATION} n'accepte plus {STRING} ou {STRING}
@@ -1215,8 +1215,8 @@ STR_CONFIG_SETTING_SIGNALSIDE_RIGHT :à droite de la
STR_CONFIG_SETTING_SHOWFINANCES :Afficher le bilan financier en fin d'année{NBSP}: {STRING}
STR_CONFIG_SETTING_SHOWFINANCES_HELPTEXT :Si activé, le bilan financier sera affiché à la fin de chaque année pour permettre un contrôle rapide de l'état de la compagnie.
STR_CONFIG_SETTING_NONSTOP_BY_DEFAULT :Les nouveaux ordres sont « sans arrêt » par défaut{NBSP}: {STRING}
-STR_CONFIG_SETTING_NONSTOP_BY_DEFAULT_HELPTEXT :Normalement, un véhicule s'arrête dans chaque station qu'il traverse. En activant ce paramètre, il traversera toutes les stations sur le chemin de sa destination finale sans s'arrêter. Noter que ce paramètre défini uniquement une valeur par défaut pour les nouveaux ordres. Les ordres peuvent par ailleurs être réglés individuellement avec un autre comportement
-STR_CONFIG_SETTING_STOP_LOCATION :Les nouveaux ordres arrêtent les trains {G 0:2 au au "à la"} {STRING} du quai par défaut
+STR_CONFIG_SETTING_NONSTOP_BY_DEFAULT_HELPTEXT :Normalement, un véhicule s'arrête dans chaque station qu'il traverse. En activant ce paramètre, il traversera toutes les stations sur le chemin de sa destination finale sans s'arrêter. Noter que ce paramètre définit uniquement une valeur par défaut pour les nouveaux ordres. Les ordres peuvent par après être réglés individuellement avec un autre comportement
+STR_CONFIG_SETTING_STOP_LOCATION :Les nouveaux ordres arrêtent les trains {G 0 au au "à la"} {STRING} du quai par défaut
STR_CONFIG_SETTING_STOP_LOCATION_HELPTEXT :Endroit du quai où un train s'arrête par défaut. "queue" signifie proche du point d'entrée. "milieu" signifie au milieu du quai. "tête" signifie à l'opposé du point d'entrée. Noter que ce paramètre défini uniquement une valeur par défaut pour les nouveaux ordres. Les ordres peuvent par ailleurs être réglés individuellement avec un autre comportement
STR_CONFIG_SETTING_STOP_LOCATION_NEAR_END :{G=f}queue
STR_CONFIG_SETTING_STOP_LOCATION_MIDDLE :{G=m}milieu
@@ -1407,7 +1407,7 @@ STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_FIRST :Premier disponi
STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_LAST :Dernier disponible
STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_MOST_USED :Le plus utilisé
STR_CONFIG_SETTING_SHOW_TRACK_RESERVATION :Afficher les réservations de chemin sur les voies{NBSP}: {STRING}
-STR_CONFIG_SETTING_SHOW_TRACK_RESERVATION_HELPTEXT :Donne une couleur différente aux voies réservées pour aider à résoudre les problèmes de trains refusant d'entrer dans des blocs basés sur le chemin
+STR_CONFIG_SETTING_SHOW_TRACK_RESERVATION_HELPTEXT :Donne une couleur différente aux voies réservées afin d'aider à résoudre les problèmes de trains refusant de s'engager dans des tronçons encadrés par des signaux de passage
STR_CONFIG_SETTING_PERSISTENT_BUILDINGTOOLS :Conserver les outils de construction actifs après usage{NBSP}: {STRING}
STR_CONFIG_SETTING_PERSISTENT_BUILDINGTOOLS_HELPTEXT :Garde les outils de construction de ponts, tunnels, etc. ouverts après usage
STR_CONFIG_SETTING_EXPENSES_LAYOUT :Regrouper les dépenses dans la fenêtre des finances{NBSP}: {STRING}
@@ -1464,15 +1464,15 @@ STR_CONFIG_SETTING_SCRIPT_MAX_OPCODES_HELPTEXT :Nombre maximum
STR_CONFIG_SETTING_SERVINT_ISPERCENT :Les intervalles de service sont en pourcentage{NBSP}: {STRING}
STR_CONFIG_SETTING_SERVINT_ISPERCENT_HELPTEXT :Choisir si l'entretien des véhicule est activé par le temps passé depuis le dernier entretien ou par la fiabilité passant sous un pourcentage de la fiabilité maximum
STR_CONFIG_SETTING_SERVINT_TRAINS :Intervalle d'entretien par défaut pour les trains{NBSP}: {STRING}
-STR_CONFIG_SETTING_SERVINT_TRAINS_HELPTEXT :Défini l'intervalle d'entretien par défaut des nouveaux véhicules ferroviaires, si aucun intervalle d'entretien n'est définit pour le véhicule
+STR_CONFIG_SETTING_SERVINT_TRAINS_HELPTEXT :Définit l'intervalle d'entretien par défaut des nouveaux véhicules ferroviaires, si aucun intervalle d'entretien n'est défini pour le véhicule
STR_CONFIG_SETTING_SERVINT_VALUE :{COMMA}{NBSP}jour{P 0 "" s}/%
STR_CONFIG_SETTING_SERVINT_DISABLED :Désactivé
STR_CONFIG_SETTING_SERVINT_ROAD_VEHICLES :Intervalle d'entretien par défaut pour les véhicules routiers{NBSP}: {STRING}
-STR_CONFIG_SETTING_SERVINT_ROAD_VEHICLES_HELPTEXT :Défini l'intervalle d'entretien par défaut des nouveaux véhicules routiers, si aucun intervalle d'entretien n'est définit pour le véhicule
+STR_CONFIG_SETTING_SERVINT_ROAD_VEHICLES_HELPTEXT :Définit l'intervalle d'entretien par défaut des nouveaux véhicules routiers, si aucun intervalle d'entretien n'est défini pour le véhicule
STR_CONFIG_SETTING_SERVINT_AIRCRAFT :Intervalle d'entretien par défaut pour les aéronefs{NBSP}: {STRING}
-STR_CONFIG_SETTING_SERVINT_AIRCRAFT_HELPTEXT :Défini l'intervalle d'entretien par défaut des nouveaux aéronefs, si aucun intervalle d'entretien n'est définit pour le véhicule
+STR_CONFIG_SETTING_SERVINT_AIRCRAFT_HELPTEXT :Définit l'intervalle d'entretien par défaut des nouveaux aéronefs, si aucun intervalle d'entretien n'est défini pour le véhicule
STR_CONFIG_SETTING_SERVINT_SHIPS :Intervalle d'entretien par défaut pour les navires{NBSP}: {STRING}
-STR_CONFIG_SETTING_SERVINT_SHIPS_HELPTEXT :Défini l'intervalle d'entretien par défaut des nouveaux navires, si aucun intervalle d'entretien n'est définit pour le véhicule
+STR_CONFIG_SETTING_SERVINT_SHIPS_HELPTEXT :Définit l'intervalle d'entretien par défaut des nouveaux navires, si aucun intervalle d'entretien n'est défini pour le véhicule
STR_CONFIG_SETTING_NOSERVICE :Désactiver l'entretien quand les pannes sont inactives{NBSP}: {STRING}
STR_CONFIG_SETTING_NOSERVICE_HELPTEXT :Lorsqu'il est activé, les véhicules ne sont pas entretenus s'ils ne peuvent pas tomber en panne
STR_CONFIG_SETTING_WAGONSPEEDLIMITS :Activer la vitesse limite des wagons{NBSP}: {STRING}
@@ -2221,7 +2221,7 @@ STR_CONTENT_DETAIL_SUBTITLE_SELECTED :{SILVER}Pré-s
STR_CONTENT_DETAIL_SUBTITLE_AUTOSELECTED :{SILVER}Cette dépendance a été sélectionnée pour être téléchargée
STR_CONTENT_DETAIL_SUBTITLE_ALREADY_HERE :{SILVER}Vous avez déjà ceci
STR_CONTENT_DETAIL_SUBTITLE_DOES_NOT_EXIST :{SILVER}Ce module est inconnu et ne peut pas être téléchargé dans OpenTTD
-STR_CONTENT_DETAIL_UPDATE :{SILVER}Ceci est un remplacement pour {G "un" "des" "une"} {STRING} existant{G 0 "" "s" "e"}
+STR_CONTENT_DETAIL_UPDATE :{SILVER}Ceci est un remplacement pour {G 0 "un" "des" "une"} {STRING} existant{G 0 "" "s" "e"}
STR_CONTENT_DETAIL_NAME :{SILVER}Nom{NBSP}: {WHITE}{STRING}
STR_CONTENT_DETAIL_VERSION :{SILVER}Version{NBSP}: {WHITE}{STRING}
STR_CONTENT_DETAIL_DESCRIPTION :{SILVER}Description{NBSP}: {WHITE}{STRING}
diff --git a/src/lang/german.txt b/src/lang/german.txt
index 3cb8402da0..4c8e9d45d3 100644
--- a/src/lang/german.txt
+++ b/src/lang/german.txt
@@ -2888,6 +2888,7 @@ STR_SPRITE_ALIGNER_PREVIOUS_BUTTON :{BLACK}Vorherig
STR_SPRITE_ALIGNER_PREVIOUS_TOOLTIP :{BLACK}Gehe zum vorherigen normalen Sprite und überspringe alle Pseudo-, Recolour- und Schriftsprites (springt ggf. vom letzten bis zum ersten Sprite)
STR_SPRITE_ALIGNER_SPRITE_TOOLTIP :{BLACK}Darstellung des aktuellen Sprites. Die Ausrichtung wird beim Zeichnen des Sprites ignoriert
STR_SPRITE_ALIGNER_MOVE_TOOLTIP :{BLACK}Bewege die Sprites und ändere dadurch die X- und Y-Offsets. Drücke Strg+Click um die Sprites 8 Blöcke weit zu bewegen.
+STR_SPRITE_ALIGNER_RESET_BUTTON :{BLACK}Setze zurück
STR_SPRITE_ALIGNER_RESET_TOOLTIP :{BLACK}Setze die aktuelle Verschiebung zurück.
STR_SPRITE_ALIGNER_OFFSETS_ABS :{BLACK}X offset: {NUM}, Y offset: {NUM} (Absolut)
STR_SPRITE_ALIGNER_OFFSETS_REL :{BLACK}X offset: {NUM}, Y offset: {NUM} (Relativ)
diff --git a/src/network/network_command.cpp b/src/network/network_command.cpp
index 72babbc772..6d131628a3 100644
--- a/src/network/network_command.cpp
+++ b/src/network/network_command.cpp
@@ -52,6 +52,10 @@ static CommandCallback * const _callback_table[] = {
/* 0x1A */ CcGame,
/* 0x1B */ CcAddVehicleNewGroup,
/* 0x1C */ CcAddPlan,
+ /* 0x1D */ CcSetVirtualTrain,
+ /* 0x1E */ CcVirtualTrainWaggonsMoved,
+ /* 0x1F */ CcDeleteVirtualTrain,
+ /* 0x20 */ CcAddVirtualEngine,
};
/**
diff --git a/src/newgrf.cpp b/src/newgrf.cpp
index 5487e03a5e..388bee71e4 100644
--- a/src/newgrf.cpp
+++ b/src/newgrf.cpp
@@ -1402,7 +1402,7 @@ static ChangeInfoResult RoadVehicleChangeInfo(uint engine, int numinfo, int prop
break;
case 0x12: // SFX
- rvi->sfx = buf->ReadByte();
+ rvi->sfx = GetNewGRFSoundID(_cur.grffile, buf->ReadByte());
break;
case PROP_ROADVEH_POWER: // Power in units of 10 HP.
@@ -1590,7 +1590,7 @@ static ChangeInfoResult ShipVehicleChangeInfo(uint engine, int numinfo, int prop
break;
case 0x10: // SFX
- svi->sfx = buf->ReadByte();
+ svi->sfx = GetNewGRFSoundID(_cur.grffile, buf->ReadByte());
break;
case 0x11: { // Cargoes available for refitting
@@ -1758,7 +1758,7 @@ static ChangeInfoResult AircraftVehicleChangeInfo(uint engine, int numinfo, int
break;
case 0x12: // SFX
- avi->sfx = buf->ReadByte();
+ avi->sfx = GetNewGRFSoundID(_cur.grffile, buf->ReadByte());
break;
case 0x13: { // Cargoes available for refitting
diff --git a/src/newgrf.h b/src/newgrf.h
index d0d64d35ec..de31906701 100644
--- a/src/newgrf.h
+++ b/src/newgrf.h
@@ -197,4 +197,6 @@ StringID MapGRFStringID(uint32 grfid, StringID str);
void ShowNewGRFError();
int CountSelectedGRFs(GRFConfig *grfconf);
+struct TemplateVehicle;
+
#endif /* NEWGRF_H */
diff --git a/src/newgrf_sound.cpp b/src/newgrf_sound.cpp
index 60ee609165..0cc113d9df 100644
--- a/src/newgrf_sound.cpp
+++ b/src/newgrf_sound.cpp
@@ -161,6 +161,22 @@ bool LoadNewGRFSound(SoundEntry *sound)
return false;
}
+/**
+ * Resolve NewGRF sound ID.
+ * @param file NewGRF to get sound from.
+ * @param sound_id GRF-specific sound ID. (GRF-local for IDs above ORIGINAL_SAMPLE_COUNT)
+ * @return Translated (global) sound ID, or INVALID_SOUND.
+ */
+SoundID GetNewGRFSoundID(const GRFFile *file, SoundID sound_id)
+{
+ /* Global sound? */
+ if (sound_id < ORIGINAL_SAMPLE_COUNT) return sound_id;
+
+ sound_id -= ORIGINAL_SAMPLE_COUNT;
+ if (file == NULL || sound_id >= file->num_sounds) return INVALID_SOUND;
+
+ return file->sound_offset + sound_id;
+}
/**
* Checks whether a NewGRF wants to play a different vehicle sound effect.
@@ -185,14 +201,10 @@ bool PlayVehicleSound(const Vehicle *v, VehicleSoundEvent event)
/* Play default sound if callback fails */
if (callback == CALLBACK_FAILED) return false;
- if (callback >= ORIGINAL_SAMPLE_COUNT) {
- callback -= ORIGINAL_SAMPLE_COUNT;
+ callback = GetNewGRFSoundID(file, callback);
- /* Play no sound if result is out of range */
- if (callback > file->num_sounds) return true;
-
- callback += file->sound_offset;
- }
+ /* Play no sound, if result is invalid */
+ if (callback == INVALID_SOUND) return true;
assert(callback < GetNumSounds());
SndPlayVehicleFx(callback, v);
@@ -207,11 +219,8 @@ bool PlayVehicleSound(const Vehicle *v, VehicleSoundEvent event)
*/
void PlayTileSound(const GRFFile *file, SoundID sound_id, TileIndex tile)
{
- if (sound_id >= ORIGINAL_SAMPLE_COUNT) {
- sound_id -= ORIGINAL_SAMPLE_COUNT;
- if (sound_id > file->num_sounds) return;
- sound_id += file->sound_offset;
- }
+ sound_id = GetNewGRFSoundID(file, sound_id);
+ if (sound_id == INVALID_SOUND) return;
assert(sound_id < GetNumSounds());
SndPlayTileFx(sound_id, tile);
diff --git a/src/newgrf_sound.h b/src/newgrf_sound.h
index 0d32953274..efded063c8 100644
--- a/src/newgrf_sound.h
+++ b/src/newgrf_sound.h
@@ -33,6 +33,7 @@ enum VehicleSoundEvent {
SoundEntry *AllocateSound(uint num);
void InitializeSoundPool();
bool LoadNewGRFSound(SoundEntry *sound);
+SoundID GetNewGRFSoundID(const struct GRFFile *file, SoundID sound_id);
SoundEntry *GetSound(SoundID sound_id);
uint GetNumSounds();
bool PlayVehicleSound(const Vehicle *v, VehicleSoundEvent event);
diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp
index ab73aeb0b3..54a5cabee8 100644
--- a/src/order_cmd.cpp
+++ b/src/order_cmd.cpp
@@ -1887,7 +1887,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 b0914ac57e..0c8eccb638 100644
--- a/src/saveload/afterload.cpp
+++ b/src/saveload/afterload.cpp
@@ -839,6 +839,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/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp
index 4641c11600..147dd7cc43 100644
--- a/src/saveload/extended_ver_sl.cpp
+++ b/src/saveload/extended_ver_sl.cpp
@@ -68,6 +68,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = {
{ XSLFI_REVERSE_AT_WAYPOINT, XSCF_NULL, 1, 1, "reverse_at_waypoint", NULL, NULL, NULL },
{ XSLFI_VEH_LIFETIME_PROFIT, XSCF_NULL, 1, 1, "veh_lifetime_profit", NULL, NULL, NULL },
{ XSLFI_LINKGRAPH_DAY_SCALE, XSCF_NULL, 1, 1, "linkgraph_day_scale", NULL, NULL, NULL },
+ { XSLFI_TEMPLATE_REPLACEMENT, XSCF_NULL, 1, 1, "template_replacement", NULL, NULL, "TRPL,TMPL" },
{ XSLFI_NULL, XSCF_NULL, 0, 0, NULL, NULL, NULL, NULL },// This is the end marker
};
diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h
index 080e5d8667..887ea20701 100644
--- a/src/saveload/extended_ver_sl.h
+++ b/src/saveload/extended_ver_sl.h
@@ -42,6 +42,7 @@ enum SlXvFeatureIndex {
XSLFI_REVERSE_AT_WAYPOINT, ///< Reverse at waypoint orders
XSLFI_VEH_LIFETIME_PROFIT, ///< Vehicle lifetime profit patch
XSLFI_LINKGRAPH_DAY_SCALE, ///< Linkgraph job duration & interval may be in non-scaled days
+ XSLFI_TEMPLATE_REPLACEMENT, ///< Template-based train replacement
XSLFI_RIFF_HEADER_60_BIT, ///< Size field in RIFF chunk header is 60 bit
XSLFI_HEIGHT_8_BIT, ///< Map tile height is 8 bit instead of 4 bit, but savegame version may be before this became true in trunk
diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp
index fb74e16b7a..c3cb12e586 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"
@@ -457,6 +459,8 @@ extern const ChunkHandler _persistent_storage_chunk_handlers[];
extern const ChunkHandler _trace_restrict_chunk_handlers[];
extern const ChunkHandler _signal_chunk_handlers[];
extern const ChunkHandler _plan_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[] = {
@@ -497,6 +501,8 @@ static const ChunkHandler * const _chunk_handlers[] = {
_trace_restrict_chunk_handlers,
_signal_chunk_handlers,
_plan_chunk_handlers,
+ _template_replacement_chunk_handlers,
+ _template_vehicle_chunk_handlers,
NULL,
};
@@ -1271,6 +1277,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;
@@ -1330,6 +1337,10 @@ static void *IntToReference(size_t index, SLRefType rt)
if (Vehicle::IsValidID(index)) return Vehicle::Get(index);
SlErrorCorrupt("Referencing invalid Vehicle");
+ case REF_TEMPLATE_VEHICLE:
+ if (TemplateVehicle::IsValidID(index)) return TemplateVehicle::Get(index);
+ SlErrorCorrupt("Referencing invalid TemplateVehicle");
+
case REF_STATION:
if (Station::IsValidID(index)) return Station::Get(index);
SlErrorCorrupt("Referencing invalid Station");
diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h
index 8df011958b..5bac216bf2 100644
--- a/src/saveload/saveload.h
+++ b/src/saveload/saveload.h
@@ -88,6 +88,7 @@ enum SLRefType {
REF_STORAGE = 9, ///< Load/save a reference to a persistent storage.
REF_LINK_GRAPH = 10, ///< Load/save a reference to a link graph.
REF_LINK_GRAPH_JOB = 11, ///< Load/save a reference to a link graph job.
+ REF_TEMPLATE_VEHICLE = 12, ///< Load/save a reference to a template vehicle
};
/** Highest possible savegame version. */
diff --git a/src/saveload/saveload_internal.h b/src/saveload/saveload_internal.h
index 2d1ae11bf7..2daaa12a56 100644
--- a/src/saveload/saveload_internal.h
+++ b/src/saveload/saveload_internal.h
@@ -28,6 +28,7 @@ const SaveLoad *GetBaseStationDescription();
void AfterLoadVehicles(bool part_of_load);
void FixupTrainLengths();
+void AfterLoadTemplateVehicles();
void AfterLoadStations();
void AfterLoadRoadStops();
void AfterLoadLabelMaps();
diff --git a/src/saveload/tbtr_template_replacement_sl.cpp b/src/saveload/tbtr_template_replacement_sl.cpp
new file mode 100644
index 0000000000..d9b1448ebe
--- /dev/null
+++ b/src/saveload/tbtr_template_replacement_sl.cpp
@@ -0,0 +1,35 @@
+#include "../stdafx.h"
+
+#include "../tbtr_template_vehicle.h"
+
+#include "saveload.h"
+
+static const SaveLoad _template_replacement_desc[] = {
+ SLE_VAR(TemplateReplacement, sel_template, SLE_UINT16),
+ SLE_VAR(TemplateReplacement, group, SLE_UINT16),
+ SLE_END()
+};
+
+static void Save_TMPL_RPLS()
+{
+ TemplateReplacement *tr;
+
+ FOR_ALL_TEMPLATE_REPLACEMENTS(tr) {
+ SlSetArrayIndex(tr->index);
+ SlObject(tr, _template_replacement_desc);
+ }
+}
+
+static void Load_TMPL_RPLS()
+{
+ int index;
+
+ while ((index = SlIterateArray()) != -1) {
+ TemplateReplacement *tr = new (index) TemplateReplacement();
+ SlObject(tr, _template_replacement_desc);
+ }
+}
+
+extern const ChunkHandler _template_replacement_chunk_handlers[] = {
+ {'TRPL', Save_TMPL_RPLS, Load_TMPL_RPLS, NULL, NULL, CH_ARRAY | CH_LAST},
+};
diff --git a/src/saveload/tbtr_template_veh_sl.cpp b/src/saveload/tbtr_template_veh_sl.cpp
new file mode 100644
index 0000000000..3c6ca9e0b6
--- /dev/null
+++ b/src/saveload/tbtr_template_veh_sl.cpp
@@ -0,0 +1,99 @@
+#include "../stdafx.h"
+
+#include "../tbtr_template_vehicle.h"
+
+#include "saveload.h"
+
+const SaveLoad* GTD() {
+
+ static const SaveLoad _template_veh_desc[] = {
+ SLE_REF(TemplateVehicle, next, REF_TEMPLATE_VEHICLE),
+
+ SLE_VAR(TemplateVehicle, reuse_depot_vehicles, SLE_UINT8),
+ SLE_VAR(TemplateVehicle, keep_remaining_vehicles, SLE_UINT8),
+ SLE_VAR(TemplateVehicle, refit_as_template, SLE_UINT8),
+
+ SLE_VAR(TemplateVehicle, owner, SLE_UINT32),
+ SLE_VAR(TemplateVehicle, owner_b, SLE_UINT8),
+
+ SLE_VAR(TemplateVehicle, engine_type, SLE_UINT16),
+ SLE_VAR(TemplateVehicle, cargo_type, SLE_UINT8),
+ SLE_VAR(TemplateVehicle, cargo_cap, SLE_UINT16),
+ SLE_VAR(TemplateVehicle, cargo_subtype, SLE_UINT8),
+
+ SLE_VAR(TemplateVehicle, subtype, SLE_UINT8),
+ SLE_VAR(TemplateVehicle, railtype, SLE_UINT8),
+
+ SLE_VAR(TemplateVehicle, index, SLE_UINT32),
+
+ SLE_VAR(TemplateVehicle, real_consist_length, SLE_UINT16),
+
+ SLE_VAR(TemplateVehicle, max_speed, SLE_UINT16),
+ SLE_VAR(TemplateVehicle, power, SLE_UINT32),
+ SLE_VAR(TemplateVehicle, weight, SLE_UINT32),
+ SLE_VAR(TemplateVehicle, max_te, SLE_UINT32),
+
+ SLE_VAR(TemplateVehicle, spritenum, SLE_UINT8),
+ SLE_VAR(TemplateVehicle, cur_image, SLE_UINT32),
+ SLE_VAR(TemplateVehicle, image_width, SLE_UINT32),
+
+ SLE_END()
+ };
+
+ static const SaveLoad * const _ret[] = {
+ _template_veh_desc,
+ };
+
+ return _ret[0];
+}
+
+static void Save_TMPLS()
+{
+ TemplateVehicle *tv;
+
+ FOR_ALL_TEMPLATES(tv) {
+ SlSetArrayIndex(tv->index);
+ SlObject(tv, GTD());
+ }
+}
+
+static void Load_TMPLS()
+{
+ int index;
+
+ while ((index = SlIterateArray()) != -1) {
+ TemplateVehicle *tv = new (index) TemplateVehicle(); //TODO:check with veh sl code
+ SlObject(tv, GTD());
+ }
+}
+
+static void Ptrs_TMPLS()
+{
+ TemplateVehicle *tv;
+ FOR_ALL_TEMPLATES(tv) {
+ SlObject(tv, GTD());
+ }
+}
+
+void AfterLoadTemplateVehicles()
+{
+ TemplateVehicle *tv;
+
+ FOR_ALL_TEMPLATES(tv) {
+ /* Reinstate the previous pointer */
+ if (tv->next != NULL) tv->next->previous = tv;
+ tv->first =NULL;
+ }
+ FOR_ALL_TEMPLATES(tv) {
+ /* Fill the first pointers */
+ if (tv->previous == NULL) {
+ for (TemplateVehicle *u = tv; u != NULL; u = u->Next()) {
+ u->first = tv;
+ }
+ }
+ }
+}
+
+extern const ChunkHandler _template_vehicle_chunk_handlers[] = {
+ {'TMPL', Save_TMPLS, Load_TMPLS, Ptrs_TMPLS, NULL, CH_ARRAY | CH_LAST},
+};
diff --git a/src/sound_type.h b/src/sound_type.h
index 72486dcac9..76fe25139e 100644
--- a/src/sound_type.h
+++ b/src/sound_type.h
@@ -119,4 +119,6 @@ static const uint ORIGINAL_SAMPLE_COUNT = 73;
typedef uint16 SoundID;
+static const SoundID INVALID_SOUND = 0xFFFF;
+
#endif /* SOUND_TYPE_H */
diff --git a/src/tbtr_template_gui_create.cpp b/src/tbtr_template_gui_create.cpp
new file mode 100644
index 0000000000..9e4cce687b
--- /dev/null
+++ b/src/tbtr_template_gui_create.cpp
@@ -0,0 +1,577 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file tbtr_template_gui_create.cpp Template-based train replacement: template creation GUI. */
+
+#include "stdafx.h"
+
+#include "gfx_func.h"
+#include "direction_type.h"
+
+#include "strings_func.h"
+#include "window_func.h"
+#include "company_func.h"
+#include "window_gui.h"
+#include "settings_func.h"
+#include "core/geometry_func.hpp"
+#include "table/sprites.h"
+#include "table/strings.h"
+#include "viewport_func.h"
+#include "window_func.h"
+#include "gui.h"
+#include "textbuf_gui.h"
+#include "command_func.h"
+#include "depot_base.h"
+#include "vehicle_gui.h"
+#include "spritecache.h"
+#include "strings_func.h"
+#include "window_func.h"
+#include "vehicle_func.h"
+#include "company_func.h"
+#include "tilehighlight_func.h"
+#include "window_gui.h"
+#include "vehiclelist.h"
+#include "order_backup.h"
+#include "group.h"
+#include "company_base.h"
+#include "train.h"
+
+#include "tbtr_template_gui_create.h"
+#include "tbtr_template_vehicle.h"
+#include "tbtr_template_vehicle_func.h"
+
+#include "safeguards.h"
+
+class TemplateReplaceWindow;
+
+// some space in front of the virtual train in the matrix
+uint16 TRAIN_FRONT_SPACE = 16;
+
+enum TemplateReplaceWindowWidgets {
+ TCW_CAPTION,
+ TCW_NEW_TMPL_PANEL,
+ TCW_INFO_PANEL,
+ TCW_SCROLLBAR_H_NEW_TMPL,
+ TCW_SCROLLBAR_V_NEW_TMPL,
+ TCW_SELL_TMPL,
+ TCW_NEW,
+ TCW_OK,
+ TCW_CANCEL,
+ TCW_REFIT,
+ TCW_CLONE,
+};
+
+static const NWidgetPart _widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_GREY),
+ NWidget(WWT_CAPTION, COLOUR_GREY, TCW_CAPTION), SetDataTip(STR_TMPL_CREATEGUI_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ NWidget(WWT_SHADEBOX, COLOUR_GREY),
+ NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
+ NWidget(WWT_STICKYBOX, COLOUR_GREY),
+ EndContainer(),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(NWID_VERTICAL),
+ NWidget(WWT_PANEL, COLOUR_GREY, TCW_NEW_TMPL_PANEL), SetMinimalSize(250, 30), SetResize(1, 0), SetScrollbar(TCW_SCROLLBAR_H_NEW_TMPL), EndContainer(),
+ NWidget(WWT_PANEL, COLOUR_GREY, TCW_INFO_PANEL), SetMinimalSize(250, 100), SetResize(1, 1), SetScrollbar(TCW_SCROLLBAR_V_NEW_TMPL), EndContainer(),
+ NWidget(NWID_HSCROLLBAR, COLOUR_GREY, TCW_SCROLLBAR_H_NEW_TMPL),
+ EndContainer(),
+ NWidget(WWT_IMGBTN, COLOUR_GREY, TCW_SELL_TMPL), SetMinimalSize(40, 40), SetDataTip(0x0, STR_NULL), SetResize(0, 1), SetFill(0, 1),
+ NWidget(NWID_VSCROLLBAR, COLOUR_GREY, TCW_SCROLLBAR_V_NEW_TMPL),
+ EndContainer(),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TCW_OK), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_CONFIRM, STR_TMPL_CONFIRM),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TCW_NEW), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_NEW, STR_TMPL_NEW),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, TCW_CLONE), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_CREATE_CLONE_VEH, STR_TMPL_CREATE_CLONE_VEH),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TCW_REFIT), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_REFIT, STR_TMPL_REFIT),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TCW_CANCEL), SetMinimalSize(52, 12), SetResize(1, 0), SetDataTip(STR_TMPL_CANCEL, STR_TMPL_CANCEL),
+ NWidget(WWT_RESIZEBOX, COLOUR_GREY),
+ EndContainer(),
+};
+
+static WindowDesc _template_create_window_desc(
+ WDP_AUTO, // window position
+ "template create window", // const char* ini_key
+ 456, 100, // window size
+ WC_CREATE_TEMPLATE, // window class
+ WC_TEMPLATEGUI_MAIN, // parent window class
+ WDF_CONSTRUCTION, // window flags
+ _widgets, lengthof(_widgets) // widgets + num widgets
+);
+
+static void TrainDepotMoveVehicle(const Vehicle *wagon, VehicleID sel, const Vehicle *head)
+{
+ const Vehicle *v = Vehicle::Get(sel);
+
+ if (v == wagon) return;
+
+ if (wagon == NULL) {
+ if (head != NULL) wagon = head->Last();
+ } else {
+ wagon = wagon->Previous();
+ if (wagon == NULL) return;
+ }
+
+ if (wagon == v) return;
+
+ DoCommandP(v->tile, v->index | ((_ctrl_pressed ? 1 : 0) << 20) | (1 << 21) , wagon == NULL ? INVALID_VEHICLE : wagon->index,
+ CMD_MOVE_RAIL_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_MOVE_VEHICLE), CcVirtualTrainWaggonsMoved);
+}
+
+class TemplateCreateWindow : public Window {
+private:
+ Scrollbar *hscroll;
+ Scrollbar *vscroll;
+ int line_height;
+ Train* virtual_train;
+ bool editMode;
+ bool *noticeParent;
+ bool *createWindowOpen; /// used to notify main window of progress (dummy way of disabling 'delete' while editing a template)
+ bool virtualTrainChangedNotice;
+ VehicleID sel;
+ VehicleID vehicle_over;
+ TemplateVehicle *editTemplate;
+
+public:
+ TemplateCreateWindow(WindowDesc* _wdesc, TemplateVehicle *to_edit, bool *notice, bool *windowOpen, int step_h) : Window(_wdesc)
+ {
+ this->line_height = step_h;
+ this->CreateNestedTree(_wdesc != NULL);
+ this->hscroll = this->GetScrollbar(TCW_SCROLLBAR_H_NEW_TMPL);
+ this->vscroll = this->GetScrollbar(TCW_SCROLLBAR_V_NEW_TMPL);
+ this->FinishInitNested(VEH_TRAIN);
+ /* a sprite */
+ this->GetWidget(TCW_SELL_TMPL)->widget_data = SPR_SELL_TRAIN;
+
+ this->owner = _local_company;
+
+ noticeParent = notice;
+ createWindowOpen = windowOpen;
+ virtualTrainChangedNotice = false;
+ this->editTemplate = to_edit;
+
+ editMode = (to_edit != NULL);
+
+ this->sel = INVALID_VEHICLE;
+ this->vehicle_over = INVALID_VEHICLE;
+
+ if (to_edit) {
+ DoCommandP(0, to_edit->index, 0, CMD_VIRTUAL_TRAIN_FROM_TEMPLATE_VEHICLE, CcSetVirtualTrain);
+ }
+
+ this->resize.step_height = 1;
+
+ UpdateButtonState();
+ }
+
+ ~TemplateCreateWindow()
+ {
+ if (virtual_train != NULL) {
+ DoCommandP(0, virtual_train->index, 0, CMD_DELETE_VIRTUAL_TRAIN);
+ virtual_train = NULL;
+ }
+
+ SetWindowClassesDirty(WC_TRAINS_LIST);
+
+ /* more cleanup */
+ *createWindowOpen = false;
+ DeleteWindowById(WC_BUILD_VIRTUAL_TRAIN, this->window_number);
+ }
+
+ void SetVirtualTrain(Train* const train)
+ {
+ if (virtual_train != NULL) {
+ DoCommandP(0, virtual_train->index, 0, CMD_DELETE_VIRTUAL_TRAIN);
+ }
+
+ virtual_train = train;
+ UpdateButtonState();
+ }
+
+ virtual void OnResize()
+ {
+ NWidgetCore *template_panel = this->GetWidget(TCW_NEW_TMPL_PANEL);
+ this->hscroll->SetCapacity(template_panel->current_x);
+
+ NWidgetCore *info_panel = this->GetWidget(TCW_INFO_PANEL);
+ this->vscroll->SetCapacity(info_panel->current_y);
+ }
+
+
+ virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
+ {
+ virtualTrainChangedNotice = true;
+ UpdateButtonState();
+ }
+
+ virtual void OnClick(Point pt, int widget, int click_count)
+ {
+ switch(widget) {
+ case TCW_NEW_TMPL_PANEL: {
+ NWidgetBase *nwi = this->GetWidget(TCW_NEW_TMPL_PANEL);
+ ClickedOnVehiclePanel(pt.x - nwi->pos_x, pt.y - nwi->pos_y);
+ break;
+ }
+ case TCW_NEW: {
+ ShowBuildVirtualTrainWindow(&virtual_train, &virtualTrainChangedNotice);
+ break;
+ }
+ case TCW_CLONE: {
+ this->SetWidgetDirty(TCW_CLONE);
+ this->ToggleWidgetLoweredState(TCW_CLONE);
+ if (this->IsWidgetLowered(TCW_CLONE)) {
+ SetObjectToPlaceWnd(SPR_CURSOR_CLONE_TRAIN, PAL_NONE, HT_VEHICLE, this);
+ } else {
+ ResetObjectToPlace();
+ }
+ break;
+ }
+ case TCW_OK: {
+ uint32 templateIndex = (editTemplate != NULL) ? editTemplate->index : INVALID_VEHICLE;
+
+ if (virtual_train != NULL) {
+ DoCommandP(0, templateIndex, virtual_train->index, CMD_REPLACE_TEMPLATE_VEHICLE);
+ virtual_train = NULL;
+ } else if (templateIndex != INVALID_VEHICLE) {
+ DoCommandP(0, templateIndex, 0, CMD_DELETE_TEMPLATE_VEHICLE);
+ }
+ delete this;
+ break;
+ }
+ case TCW_CANCEL: {
+ delete this;
+ break;
+ }
+ case TCW_REFIT: {
+ if (virtual_train != NULL) {
+ ShowVehicleRefitWindow(virtual_train, INVALID_VEH_ORDER_ID, this, false, true);
+ }
+ break;
+ }
+ }
+ }
+
+ virtual bool OnVehicleSelect(const Vehicle *v)
+ {
+ // throw away the current virtual train
+ if (virtual_train != NULL) {
+ DoCommandP(0, virtual_train->index, 0, CMD_DELETE_VIRTUAL_TRAIN);
+ virtual_train = NULL;
+ }
+
+ // create a new one
+ DoCommandP(0, v->index, 0, CMD_VIRTUAL_TRAIN_FROM_TRAIN, CcSetVirtualTrain);
+ this->ToggleWidgetLoweredState(TCW_CLONE);
+ ResetObjectToPlace();
+ this->SetDirty();
+
+ return true;
+ }
+
+ virtual void DrawWidget(const Rect &r, int widget) const
+ {
+ switch(widget) {
+ case TCW_NEW_TMPL_PANEL: {
+ if (this->virtual_train) {
+ DrawTrainImage(virtual_train, r.left+TRAIN_FRONT_SPACE, r.right - 25, r.top + 2, this->sel, EIT_PURCHASE, this->hscroll->GetPosition(), this->vehicle_over);
+ SetDParam(0, CeilDiv(virtual_train->gcache.cached_total_length * 10, TILE_SIZE));
+ SetDParam(1, 1);
+ DrawString(r.left, r.right, r.top, STR_TINY_BLACK_DECIMAL, TC_BLACK, SA_RIGHT);
+ }
+ break;
+ }
+ case TCW_INFO_PANEL: {
+ if (this->virtual_train) {
+ DrawPixelInfo tmp_dpi, *old_dpi;
+
+ if (!FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.right - r.left, r.bottom - r.top)) break;
+
+ old_dpi = _cur_dpi;
+ _cur_dpi = &tmp_dpi;
+
+ /* Draw vehicle performance info */
+ const GroundVehicleCache *gcache = this->virtual_train->GetGroundVehicleCache();
+ SetDParam(2, this->virtual_train->GetDisplayMaxSpeed());
+ SetDParam(1, gcache->cached_power);
+ SetDParam(0, gcache->cached_weight);
+ SetDParam(3, gcache->cached_max_te / 1000);
+ DrawString(8, r.right, 4 - this->vscroll->GetPosition(), STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE);
+ /* Draw cargo summary */
+ CargoArray cargo_caps;
+ for (const Train *tmp = this->virtual_train; tmp != NULL; tmp = tmp->Next()) {
+ cargo_caps[tmp->cargo_type] += tmp->cargo_cap;
+ }
+ int y = 30 - this->vscroll->GetPosition();
+ for (CargoID i = 0; i < NUM_CARGO; ++i) {
+ if (cargo_caps[i] > 0) {
+ SetDParam(0, i);
+ SetDParam(1, cargo_caps[i]);
+ DrawString(8, r.right, y, STR_TMPL_CARGO_SUMMARY, TC_LIGHT_BLUE, SA_LEFT);
+ y += this->line_height / 3;
+ }
+ }
+
+ _cur_dpi = old_dpi;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ virtual void OnTick()
+ {
+ if (virtualTrainChangedNotice) {
+ this->SetDirty();
+ virtualTrainChangedNotice = false;
+ UpdateButtonState();
+ }
+ }
+
+ virtual void OnDragDrop(Point pt, int widget)
+ {
+ switch (widget) {
+ case TCW_NEW_TMPL_PANEL: {
+ const Vehicle *v = NULL;
+ VehicleID sel = this->sel;
+
+ this->sel = INVALID_VEHICLE;
+ this->SetDirty();
+
+ NWidgetBase *nwi = this->GetWidget(TCW_NEW_TMPL_PANEL);
+ GetDepotVehiclePtData gdvp = { NULL, NULL };
+
+ if (this->GetVehicleFromDepotWndPt(pt.x - nwi->pos_x, pt.y - nwi->pos_y, &v, &gdvp) == MODE_DRAG_VEHICLE && sel != INVALID_VEHICLE) {
+ if (gdvp.wagon == NULL || gdvp.wagon->index != sel) {
+ this->vehicle_over = INVALID_VEHICLE;
+ TrainDepotMoveVehicle(gdvp.wagon, sel, gdvp.head);
+ }
+ }
+ break;
+ }
+ case TCW_SELL_TMPL: {
+ if (this->IsWidgetDisabled(widget)) return;
+ if (this->sel == INVALID_VEHICLE) return;
+
+ int sell_cmd = (_ctrl_pressed) ? 1 : 0;
+
+ Train* train_to_delete = Train::Get(this->sel);
+
+ if (virtual_train == train_to_delete) {
+ virtual_train = (_ctrl_pressed) ? NULL : virtual_train->GetNextUnit();
+ }
+
+ DoCommandP(0, this->sel | (sell_cmd << 20) | (1 << 21), 0, GetCmdSellVeh(VEH_TRAIN));
+
+ this->sel = INVALID_VEHICLE;
+
+ this->SetDirty();
+ UpdateButtonState();
+ break;
+ }
+ default:
+ this->sel = INVALID_VEHICLE;
+ this->SetDirty();
+ break;
+ }
+ _cursor.vehchain = false;
+ this->sel = INVALID_VEHICLE;
+ this->SetDirty();
+ }
+
+ virtual void OnMouseDrag(Point pt, int widget)
+ {
+ if (this->sel == INVALID_VEHICLE) return;
+ /* A rail vehicle is dragged.. */
+ if (widget != TCW_NEW_TMPL_PANEL) { // ..outside of the depot matrix.
+ if (this->vehicle_over != INVALID_VEHICLE) {
+ this->vehicle_over = INVALID_VEHICLE;
+ this->SetWidgetDirty(TCW_NEW_TMPL_PANEL);
+ }
+ return;
+ }
+
+ NWidgetBase *matrix = this->GetWidget(widget);
+ const Vehicle *v = NULL;
+ GetDepotVehiclePtData gdvp = {NULL, NULL};
+
+ if (this->GetVehicleFromDepotWndPt(pt.x - matrix->pos_x, pt.y - matrix->pos_y, &v, &gdvp) != MODE_DRAG_VEHICLE) return;
+ VehicleID new_vehicle_over = INVALID_VEHICLE;
+ if (gdvp.head != NULL) {
+ if (gdvp.wagon == NULL && gdvp.head->Last()->index != this->sel) { // ..at the end of the train.
+ /* NOTE: As a wagon can't be moved at the begin of a train, head index isn't used to mark a drag-and-drop
+ * destination inside a train. This head index is then used to indicate that a wagon is inserted at
+ * the end of the train.
+ */
+ new_vehicle_over = gdvp.head->index;
+ } else if (gdvp.wagon != NULL && gdvp.head != gdvp.wagon &&
+ gdvp.wagon->index != this->sel &&
+ gdvp.wagon->Previous()->index != this->sel) { // ..over an existing wagon.
+ new_vehicle_over = gdvp.wagon->index;
+ }
+ }
+ if (this->vehicle_over == new_vehicle_over) return;
+
+ this->vehicle_over = new_vehicle_over;
+ this->SetWidgetDirty(widget);
+ }
+
+ virtual void OnPaint()
+ {
+ uint min_width = 32;
+ uint min_height = 30;
+ uint width = 0;
+ uint height = 30;
+ CargoArray cargo_caps;
+
+ if (virtual_train != NULL) {
+ for (Train *train = virtual_train; train != NULL; train = train->Next()) {
+ width += train->GetDisplayImageWidth();
+ cargo_caps[train->cargo_type] += train->cargo_cap;
+ }
+
+ for (CargoID i = 0; i < NUM_CARGO; ++i) {
+ if (cargo_caps[i] > 0) {
+ height += this->line_height / 3;
+ }
+ }
+ }
+
+ min_width = max(min_width, width);
+ this->hscroll->SetCount(min_width + 50);
+
+ min_height = max(min_height, height);
+ this->vscroll->SetCount(min_height);
+
+ this->DrawWidgets();
+ }
+
+ struct GetDepotVehiclePtData {
+ const Vehicle *head;
+ const Vehicle *wagon;
+ };
+
+ enum DepotGUIAction {
+ MODE_ERROR,
+ MODE_DRAG_VEHICLE,
+ MODE_SHOW_VEHICLE,
+ MODE_START_STOP,
+ };
+
+ uint count_width;
+ uint header_width;
+
+ DepotGUIAction GetVehicleFromDepotWndPt(int x, int y, const Vehicle **veh, GetDepotVehiclePtData *d) const
+ {
+ const NWidgetCore *matrix_widget = this->GetWidget(TCW_NEW_TMPL_PANEL);
+ /* In case of RTL the widgets are swapped as a whole */
+ if (_current_text_dir == TD_RTL) x = matrix_widget->current_x - x;
+
+ x -= TRAIN_FRONT_SPACE;
+
+ uint xm = x;
+
+ bool wagon = false;
+
+ x += this->hscroll->GetPosition();
+ const Train *v = virtual_train;
+ d->head = d->wagon = v;
+
+ if (xm <= this->header_width) {
+ if (wagon) return MODE_ERROR;
+
+ return MODE_SHOW_VEHICLE;
+ }
+
+ /* Account for the header */
+ x -= this->header_width;
+
+ /* find the vehicle in this row that was clicked */
+ for (; v != NULL; v = v->Next()) {
+ x -= v->GetDisplayImageWidth();
+ if (x < 0) break;
+ }
+
+ d->wagon = (v != NULL ? v->GetFirstEnginePart() : NULL);
+
+ return MODE_DRAG_VEHICLE;
+ }
+
+ void ClickedOnVehiclePanel(int x, int y)
+ {
+ GetDepotVehiclePtData gdvp = { NULL, NULL };
+ const Vehicle *v = NULL;
+ this->GetVehicleFromDepotWndPt(x, y, &v, &gdvp);
+
+ v = gdvp.wagon;
+
+ if (v != NULL && VehicleClicked(v)) return;
+ VehicleID sel = this->sel;
+
+ if (sel != INVALID_VEHICLE) {
+ this->sel = INVALID_VEHICLE;
+ TrainDepotMoveVehicle(v, sel, gdvp.head);
+ } else if (v != NULL) {
+ int image = v->GetImage(_current_text_dir == TD_RTL ? DIR_E : DIR_W, EIT_PURCHASE);
+ SetObjectToPlaceWnd(image, GetVehiclePalette(v), HT_DRAG, this);
+
+ this->sel = v->index;
+ this->SetDirty();
+
+ _cursor.short_vehicle_offset = v->IsGroundVehicle() ? 16 - v->GetGroundVehicleCache()->cached_veh_length * 2 : 0;
+ _cursor.vehchain = _ctrl_pressed;
+ }
+ }
+
+ void RearrangeVirtualTrain()
+ {
+ virtual_train = virtual_train->First();
+ }
+
+
+ void UpdateButtonState()
+ {
+ this->SetWidgetDisabledState(TCW_REFIT, virtual_train == NULL);
+ }
+};
+
+void ShowTemplateCreateWindow(TemplateVehicle *to_edit, bool *noticeParent, bool *createWindowOpen, int step_h)
+{
+ if (BringWindowToFrontById(WC_CREATE_TEMPLATE, VEH_TRAIN) != NULL) return;
+ new TemplateCreateWindow(&_template_create_window_desc, to_edit, noticeParent, createWindowOpen, step_h);
+}
+
+void CcSetVirtualTrain(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
+{
+ if (result.Failed()) return;
+
+ Window* window = FindWindowById(WC_CREATE_TEMPLATE, 0);
+ if (window) {
+ Train* train = Train::From(Vehicle::Get(_new_vehicle_id));
+ ((TemplateCreateWindow*)window)->SetVirtualTrain(train);
+ window->InvalidateData();
+ }
+}
+
+void CcVirtualTrainWaggonsMoved(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
+{
+ if (result.Failed()) return;
+
+ Window* window = FindWindowById(WC_CREATE_TEMPLATE, 0);
+ if (window) {
+ ((TemplateCreateWindow*)window)->RearrangeVirtualTrain();
+ window->InvalidateData();
+ }
+}
+
+void CcDeleteVirtualTrain(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
+{
+ VehicleID virtual_train_id = p2;
+ DoCommandP(0, virtual_train_id, 0, CMD_DELETE_VIRTUAL_TRAIN);
+}
diff --git a/src/tbtr_template_gui_create.h b/src/tbtr_template_gui_create.h
new file mode 100644
index 0000000000..d95cf53bdf
--- /dev/null
+++ b/src/tbtr_template_gui_create.h
@@ -0,0 +1,20 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file tbtr_template_gui_create.h Template-based train replacement: template creation GUI header. */
+
+#ifndef TEMPLATE_GUI_CREATE
+#define TEMPLATE_GUI_CREATE
+
+#include "tbtr_template_vehicle.h"
+#include "tbtr_template_gui_create_virtualtrain.h"
+
+void ShowTemplateCreateWindow(TemplateVehicle*, bool*, bool*, int);
+
+#endif
diff --git a/src/tbtr_template_gui_create_virtualtrain.cpp b/src/tbtr_template_gui_create_virtualtrain.cpp
new file mode 100644
index 0000000000..de3f537327
--- /dev/null
+++ b/src/tbtr_template_gui_create_virtualtrain.cpp
@@ -0,0 +1,837 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file tbtr_template_gui_create_virtualtrain.cpp Template-based train replacement: template creation vehicle build GUI. */
+
+#include "stdafx.h"
+#include "engine_base.h"
+#include "engine_func.h"
+#include "station_base.h"
+#include "articulated_vehicles.h"
+#include "textbuf_gui.h"
+#include "command_func.h"
+#include "company_func.h"
+#include "vehicle_gui.h"
+#include "newgrf_engine.h"
+#include "newgrf_text.h"
+#include "group.h"
+#include "string_func.h"
+#include "strings_func.h"
+#include "window_func.h"
+#include "date_func.h"
+#include "vehicle_func.h"
+#include "widgets/dropdown_func.h"
+#include "engine_gui.h"
+#include "cargotype.h"
+#include "core/geometry_func.hpp"
+#include "vehicle_gui.h"
+#include "tbtr_template_gui_create_virtualtrain.h"
+
+#include "widgets/build_vehicle_widget.h"
+
+#include "table/strings.h"
+
+#include "safeguards.h"
+
+static const NWidgetPart _nested_build_vehicle_widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_GREY),
+ NWidget(WWT_CAPTION, COLOUR_GREY, WID_BV_CAPTION), SetDataTip(STR_WHITE_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ NWidget(WWT_SHADEBOX, COLOUR_GREY),
+ NWidget(WWT_STICKYBOX, COLOUR_GREY),
+ EndContainer(),
+ NWidget(WWT_PANEL, COLOUR_GREY),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(NWID_VERTICAL),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SORT_ASCENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0),
+ NWidget(NWID_SPACER), SetFill(1, 1),
+ EndContainer(),
+ NWidget(NWID_VERTICAL),
+ NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_SORT_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
+ NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_CARGO_FILTER_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_FILTER_CRITERIA),
+ EndContainer(),
+ EndContainer(),
+ EndContainer(),
+ /* Vehicle list. */
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_MATRIX, COLOUR_GREY, WID_BV_LIST), SetResize(1, 1), SetFill(1, 0), SetDataTip(0x101, STR_NULL), SetScrollbar(WID_BV_SCROLLBAR),
+ NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BV_SCROLLBAR),
+ EndContainer(),
+ /* Panel with details. */
+ NWidget(WWT_PANEL, COLOUR_GREY, WID_BV_PANEL), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
+ /* Build/rename buttons, resize button. */
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_BUILD), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_TMPL_CONFIRM, STR_TMPL_CONFIRM),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_RENAME), SetResize(1, 0), SetFill(1, 0),
+ NWidget(WWT_RESIZEBOX, COLOUR_GREY),
+ EndContainer(),
+};
+
+/** Special cargo filter criteria */
+static const CargoID CF_ANY = CT_NO_REFIT; ///< Show all vehicles independent of carried cargo (i.e. no filtering)
+static const CargoID CF_NONE = CT_INVALID; ///< Show only vehicles which do not carry cargo (e.g. train engines)
+
+static bool _internal_sort_order; ///< false = descending, true = ascending
+static byte _last_sort_criteria[] = {0, 0, 0, 0};
+static bool _last_sort_order[] = {false, false, false, false};
+static CargoID _last_filter_criteria[] = {CF_ANY, CF_ANY, CF_ANY, CF_ANY};
+
+/**
+ * Determines order of engines by engineID
+ * @param *a first engine to compare
+ * @param *b second engine to compare
+ * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
+ */
+static int CDECL EngineNumberSorter(const EngineID *a, const EngineID *b)
+{
+ int r = Engine::Get(*a)->list_position - Engine::Get(*b)->list_position;
+
+ return _internal_sort_order ? -r : r;
+}
+
+/**
+ * Determines order of engines by introduction date
+ * @param *a first engine to compare
+ * @param *b second engine to compare
+ * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
+ */
+static int CDECL EngineIntroDateSorter(const EngineID *a, const EngineID *b)
+{
+ const int va = Engine::Get(*a)->intro_date;
+ const int vb = Engine::Get(*b)->intro_date;
+ const int r = va - vb;
+
+ /* Use EngineID to sort instead since we want consistent sorting */
+ if (r == 0) return EngineNumberSorter(a, b);
+ return _internal_sort_order ? -r : r;
+}
+
+/**
+ * Determines order of engines by name
+ * @param *a first engine to compare
+ * @param *b second engine to compare
+ * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
+ */
+static int CDECL EngineNameSorter(const EngineID *a, const EngineID *b)
+{
+ static EngineID last_engine[2] = { INVALID_ENGINE, INVALID_ENGINE };
+ static char last_name[2][64] = { "\0", "\0" };
+
+ const EngineID va = *a;
+ const EngineID vb = *b;
+
+ if (va != last_engine[0]) {
+ last_engine[0] = va;
+ SetDParam(0, va);
+ GetString(last_name[0], STR_ENGINE_NAME, lastof(last_name[0]));
+ }
+
+ if (vb != last_engine[1]) {
+ last_engine[1] = vb;
+ SetDParam(0, vb);
+ GetString(last_name[1], STR_ENGINE_NAME, lastof(last_name[1]));
+ }
+
+ int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
+
+ /* Use EngineID to sort instead since we want consistent sorting */
+ if (r == 0) return EngineNumberSorter(a, b);
+ return _internal_sort_order ? -r : r;
+}
+
+/**
+ * Determines order of engines by reliability
+ * @param *a first engine to compare
+ * @param *b second engine to compare
+ * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
+ */
+static int CDECL EngineReliabilitySorter(const EngineID *a, const EngineID *b)
+{
+ const int va = Engine::Get(*a)->reliability;
+ const int vb = Engine::Get(*b)->reliability;
+ const int r = va - vb;
+
+ /* Use EngineID to sort instead since we want consistent sorting */
+ if (r == 0) return EngineNumberSorter(a, b);
+ return _internal_sort_order ? -r : r;
+}
+
+/**
+ * Determines order of engines by purchase cost
+ * @param *a first engine to compare
+ * @param *b second engine to compare
+ * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
+ */
+static int CDECL EngineCostSorter(const EngineID *a, const EngineID *b)
+{
+ Money va = Engine::Get(*a)->GetCost();
+ Money vb = Engine::Get(*b)->GetCost();
+ int r = ClampToI32(va - vb);
+
+ /* Use EngineID to sort instead since we want consistent sorting */
+ if (r == 0) return EngineNumberSorter(a, b);
+ return _internal_sort_order ? -r : r;
+}
+
+/**
+ * Determines order of engines by speed
+ * @param *a first engine to compare
+ * @param *b second engine to compare
+ * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
+ */
+static int CDECL EngineSpeedSorter(const EngineID *a, const EngineID *b)
+{
+ int va = Engine::Get(*a)->GetDisplayMaxSpeed();
+ int vb = Engine::Get(*b)->GetDisplayMaxSpeed();
+ int r = va - vb;
+
+ /* Use EngineID to sort instead since we want consistent sorting */
+ if (r == 0) return EngineNumberSorter(a, b);
+ return _internal_sort_order ? -r : r;
+}
+
+/**
+ * Determines order of engines by power
+ * @param *a first engine to compare
+ * @param *b second engine to compare
+ * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
+ */
+static int CDECL EnginePowerSorter(const EngineID *a, const EngineID *b)
+{
+ int va = Engine::Get(*a)->GetPower();
+ int vb = Engine::Get(*b)->GetPower();
+ int r = va - vb;
+
+ /* Use EngineID to sort instead since we want consistent sorting */
+ if (r == 0) return EngineNumberSorter(a, b);
+ return _internal_sort_order ? -r : r;
+}
+
+/**
+ * Determines order of engines by tractive effort
+ * @param *a first engine to compare
+ * @param *b second engine to compare
+ * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
+ */
+static int CDECL EngineTractiveEffortSorter(const EngineID *a, const EngineID *b)
+{
+ int va = Engine::Get(*a)->GetDisplayMaxTractiveEffort();
+ int vb = Engine::Get(*b)->GetDisplayMaxTractiveEffort();
+ int r = va - vb;
+
+ /* Use EngineID to sort instead since we want consistent sorting */
+ if (r == 0) return EngineNumberSorter(a, b);
+ return _internal_sort_order ? -r : r;
+}
+
+/**
+ * Determines order of engines by running costs
+ * @param *a first engine to compare
+ * @param *b second engine to compare
+ * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
+ */
+static int CDECL EngineRunningCostSorter(const EngineID *a, const EngineID *b)
+{
+ Money va = Engine::Get(*a)->GetRunningCost();
+ Money vb = Engine::Get(*b)->GetRunningCost();
+ int r = ClampToI32(va - vb);
+
+ /* Use EngineID to sort instead since we want consistent sorting */
+ if (r == 0) return EngineNumberSorter(a, b);
+ return _internal_sort_order ? -r : r;
+}
+
+/**
+ * Determines order of engines by running costs
+ * @param *a first engine to compare
+ * @param *b second engine to compare
+ * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
+ */
+static int CDECL EnginePowerVsRunningCostSorter(const EngineID *a, const EngineID *b)
+{
+ const Engine *e_a = Engine::Get(*a);
+ const Engine *e_b = Engine::Get(*b);
+
+ /* Here we are using a few tricks to get the right sort.
+ * We want power/running cost, but since we usually got higher running cost than power and we store the result in an int,
+ * we will actually calculate cunning cost/power (to make it more than 1).
+ * Because of this, the return value have to be reversed as well and we return b - a instead of a - b.
+ * Another thing is that both power and running costs should be doubled for multiheaded engines.
+ * Since it would be multipling with 2 in both numerator and denumerator, it will even themselves out and we skip checking for multiheaded. */
+ Money va = (e_a->GetRunningCost()) / max(1U, (uint)e_a->GetPower());
+ Money vb = (e_b->GetRunningCost()) / max(1U, (uint)e_b->GetPower());
+ int r = ClampToI32(vb - va);
+
+ /* Use EngineID to sort instead since we want consistent sorting */
+ if (r == 0) return EngineNumberSorter(a, b);
+ return _internal_sort_order ? -r : r;
+}
+
+/* Train sorting functions */
+
+/**
+ * Determines order of train engines by capacity
+ * @param *a first engine to compare
+ * @param *b second engine to compare
+ * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
+ */
+static int CDECL TrainEngineCapacitySorter(const EngineID *a, const EngineID *b)
+{
+ const RailVehicleInfo *rvi_a = RailVehInfo(*a);
+ const RailVehicleInfo *rvi_b = RailVehInfo(*b);
+
+ int va = GetTotalCapacityOfArticulatedParts(*a) * (rvi_a->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
+ int vb = GetTotalCapacityOfArticulatedParts(*b) * (rvi_b->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
+ int r = va - vb;
+
+ /* Use EngineID to sort instead since we want consistent sorting */
+ if (r == 0) return EngineNumberSorter(a, b);
+ return _internal_sort_order ? -r : r;
+}
+
+/**
+ * Determines order of train engines by engine / wagon
+ * @param *a first engine to compare
+ * @param *b second engine to compare
+ * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
+ */
+static int CDECL TrainEnginesThenWagonsSorter(const EngineID *a, const EngineID *b)
+{
+ int val_a = (RailVehInfo(*a)->railveh_type == RAILVEH_WAGON ? 1 : 0);
+ int val_b = (RailVehInfo(*b)->railveh_type == RAILVEH_WAGON ? 1 : 0);
+ int r = val_a - val_b;
+
+ /* Use EngineID to sort instead since we want consistent sorting */
+ if (r == 0) return EngineNumberSorter(a, b);
+ return _internal_sort_order ? -r : r;
+}
+
+/** Sort functions for the vehicle sort criteria, for each vehicle type. */
+static EngList_SortTypeFunction * const _sorter[][11] = {{
+ /* Trains */
+ &EngineNumberSorter,
+ &EngineCostSorter,
+ &EngineSpeedSorter,
+ &EnginePowerSorter,
+ &EngineTractiveEffortSorter,
+ &EngineIntroDateSorter,
+ &EngineNameSorter,
+ &EngineRunningCostSorter,
+ &EnginePowerVsRunningCostSorter,
+ &EngineReliabilitySorter,
+ &TrainEngineCapacitySorter,
+}};
+
+static const StringID _sort_listing[][12] = {{
+ /* Trains */
+ STR_SORT_BY_ENGINE_ID,
+ STR_SORT_BY_COST,
+ STR_SORT_BY_MAX_SPEED,
+ STR_SORT_BY_POWER,
+ STR_SORT_BY_TRACTIVE_EFFORT,
+ STR_SORT_BY_INTRO_DATE,
+ STR_SORT_BY_NAME,
+ STR_SORT_BY_RUNNING_COST,
+ STR_SORT_BY_POWER_VS_RUNNING_COST,
+ STR_SORT_BY_RELIABILITY,
+ STR_SORT_BY_CARGO_CAPACITY,
+ INVALID_STRING_ID
+}};
+
+/** Cargo filter functions */
+static bool CDECL CargoFilter(const EngineID *eid, const CargoID cid)
+{
+ if (cid == CF_ANY) return true;
+ uint32 refit_mask = GetUnionOfArticulatedRefitMasks(*eid, true);
+ return (cid == CF_NONE ? refit_mask == 0 : HasBit(refit_mask, cid));
+}
+
+static GUIEngineList::FilterFunction * const _filter_funcs[] = {
+ &CargoFilter,
+};
+
+/**
+ * Engine drawing loop
+ * @param type Type of vehicle (VEH_*)
+ * @param l The left most location of the list
+ * @param r The right most location of the list
+ * @param y The top most location of the list
+ * @param eng_list What engines to draw
+ * @param min where to start in the list
+ * @param max where in the list to end
+ * @param selected_id what engine to highlight as selected, if any
+ * @param show_count Whether to show the amount of engines or not
+ * @param selected_group the group to list the engines of
+ */
+static void DrawEngineList(VehicleType type, int l, int r, int y, const GUIEngineList *eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group)
+{
+ static const int sprite_widths[] = { 60, 60, 76, 67 };
+ static const int sprite_y_offsets[] = { -1, -1, -2, -2 };
+
+ /* Obligatory sanity checks! */
+ assert((uint)type < lengthof(sprite_widths));
+ assert_compile(lengthof(sprite_y_offsets) == lengthof(sprite_widths));
+ assert(max <= eng_list->Length());
+
+ bool rtl = _current_text_dir == TD_RTL;
+ int step_size = GetEngineListHeight(type);
+ int sprite_width = sprite_widths[type];
+
+ int sprite_x = (rtl ? r - sprite_width / 2 : l + sprite_width / 2) - 1;
+ int sprite_y_offset = sprite_y_offsets[type] + step_size / 2;
+
+ int text_left = l + (rtl ? WD_FRAMERECT_LEFT : sprite_width);
+ int text_right = r - (rtl ? sprite_width : WD_FRAMERECT_RIGHT);
+
+ int normal_text_y_offset = (step_size - FONT_HEIGHT_NORMAL) / 2;
+ int small_text_y_offset = step_size - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1;
+
+ for (; min < max; min++, y += step_size) {
+ const EngineID engine = (*eng_list)[min];
+ /* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */
+ const uint num_engines = GetGroupNumEngines(_local_company, selected_group, engine);
+
+ SetDParam(0, engine);
+ DrawString(text_left, text_right, y + normal_text_y_offset, STR_ENGINE_NAME, engine == selected_id ? TC_WHITE : TC_BLACK);
+ DrawVehicleEngine(l, r, sprite_x, y + sprite_y_offset, engine, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(engine, _local_company), EIT_PURCHASE);
+ if (show_count) {
+ SetDParam(0, num_engines);
+ DrawString(text_left, text_right, y + small_text_y_offset, STR_TINY_BLACK_COMA, TC_FROMSTRING, SA_RIGHT);
+ }
+ }
+}
+
+
+struct BuildVirtualTrainWindow : Window {
+ VehicleType vehicle_type;
+ union {
+ RailTypeByte railtype;
+ RoadTypes roadtypes;
+ } filter;
+ bool descending_sort_order;
+ byte sort_criteria;
+ bool listview_mode;
+ EngineID sel_engine;
+ EngineID rename_engine;
+ GUIEngineList eng_list;
+ CargoID cargo_filter[NUM_CARGO + 2]; ///< Available cargo filters; CargoID or CF_ANY or CF_NONE
+ StringID cargo_filter_texts[NUM_CARGO + 3]; ///< Texts for filter_cargo, terminated by INVALID_STRING_ID
+ byte cargo_filter_criteria; ///< Selected cargo filter
+ int details_height; ///< Minimal needed height of the details panels (found so far).
+ Scrollbar *vscroll;
+ Train **virtual_train; ///< the virtual train that is currently being created
+ bool *noticeParent;
+
+ BuildVirtualTrainWindow(WindowDesc *desc, Train **vt, bool *notice) : Window(desc)
+ {
+ this->vehicle_type = VEH_TRAIN;
+ this->window_number = 0;
+
+ this->sel_engine = INVALID_ENGINE;
+
+ this->sort_criteria = _last_sort_criteria[VEH_TRAIN];
+ this->descending_sort_order = _last_sort_order[VEH_TRAIN];
+
+ this->filter.railtype = RAILTYPE_END;
+
+ this->listview_mode = (this->window_number <= VEH_END);
+
+ this->CreateNestedTree(desc);
+
+ this->vscroll = this->GetScrollbar(WID_BV_SCROLLBAR);
+
+ NWidgetCore *widget = this->GetWidget(WID_BV_LIST);
+
+ widget = this->GetWidget(WID_BV_BUILD);
+
+ widget = this->GetWidget(WID_BV_RENAME);
+ widget->widget_data = STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON + VEH_TRAIN;
+ widget->tool_tip = STR_BUY_VEHICLE_TRAIN_RENAME_TOOLTIP + VEH_TRAIN;
+
+ this->details_height = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9) * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
+
+ this->FinishInitNested(VEH_TRAIN);
+
+ this->owner = _local_company;
+
+ this->eng_list.ForceRebuild();
+ this->GenerateBuildList();
+
+ if (this->eng_list.Length() > 0) this->sel_engine = this->eng_list[0];
+
+ this->virtual_train = vt;
+ this->noticeParent = notice;
+ }
+
+ /** Populate the filter list and set the cargo filter criteria. */
+ void SetCargoFilterArray()
+ {
+ uint filter_items = 0;
+
+ /* Add item for disabling filtering. */
+ this->cargo_filter[filter_items] = CF_ANY;
+ this->cargo_filter_texts[filter_items] = STR_PURCHASE_INFO_ALL_TYPES;
+ filter_items++;
+
+ /* Add item for vehicles not carrying anything, e.g. train engines.
+ * This could also be useful for eyecandy vehicles of other types, but is likely too confusing for joe, */
+ if (this->vehicle_type == VEH_TRAIN) {
+ this->cargo_filter[filter_items] = CF_NONE;
+ this->cargo_filter_texts[filter_items] = STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE;
+ filter_items++;
+ }
+
+ /* Collect available cargo types for filtering. */
+ const CargoSpec *cs;
+ FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) {
+ this->cargo_filter[filter_items] = cs->Index();
+ this->cargo_filter_texts[filter_items] = cs->name;
+ filter_items++;
+ }
+
+ /* Terminate the filter list. */
+ this->cargo_filter_texts[filter_items] = INVALID_STRING_ID;
+
+ /* If not found, the cargo criteria will be set to all cargoes. */
+ this->cargo_filter_criteria = 0;
+
+ /* Find the last cargo filter criteria. */
+ for (uint i = 0; i < filter_items; ++i) {
+ if (this->cargo_filter[i] == _last_filter_criteria[this->vehicle_type]) {
+ this->cargo_filter_criteria = i;
+ break;
+ }
+ }
+
+ this->eng_list.SetFilterFuncs(_filter_funcs);
+ this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
+ }
+
+ void OnInit()
+ {
+ this->SetCargoFilterArray();
+ }
+
+ /** Filter the engine list against the currently selected cargo filter */
+ void FilterEngineList()
+ {
+ this->eng_list.Filter(this->cargo_filter[this->cargo_filter_criteria]);
+ if (0 == this->eng_list.Length()) { // no engine passed through the filter, invalidate the previously selected engine
+ this->sel_engine = INVALID_ENGINE;
+ } else if (!this->eng_list.Contains(this->sel_engine)) { // previously selected engine didn't pass the filter, select the first engine of the list
+ this->sel_engine = this->eng_list[0];
+ }
+ }
+
+ /** Filter a single engine */
+ bool FilterSingleEngine(EngineID eid)
+ {
+ CargoID filter_type = this->cargo_filter[this->cargo_filter_criteria];
+ return (filter_type == CF_ANY || CargoFilter(&eid, filter_type));
+ }
+
+ /* Figure out what train EngineIDs to put in the list */
+ void GenerateBuildTrainList()
+ {
+ EngineID sel_id = INVALID_ENGINE;
+ int num_engines = 0;
+ int num_wagons = 0;
+
+ this->filter.railtype = (this->listview_mode) ? RAILTYPE_END : GetRailType(this->window_number);
+
+ this->eng_list.Clear();
+
+ /* Make list of all available train engines and wagons.
+ * Also check to see if the previously selected engine is still available,
+ * and if not, reset selection to INVALID_ENGINE. This could be the case
+ * when engines become obsolete and are removed */
+ const Engine *e;
+ FOR_ALL_ENGINES_OF_TYPE(e, VEH_TRAIN) {
+ EngineID eid = e->index;
+ const RailVehicleInfo *rvi = &e->u.rail;
+
+ if (this->filter.railtype != RAILTYPE_END && !HasPowerOnRail(rvi->railtype, this->filter.railtype)) continue;
+ if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue;
+
+ /* Filter now! So num_engines and num_wagons is valid */
+ if (!FilterSingleEngine(eid)) continue;
+
+ *this->eng_list.Append() = eid;
+
+ if (rvi->railveh_type != RAILVEH_WAGON) {
+ num_engines++;
+ } else {
+ num_wagons++;
+ }
+
+ if (eid == this->sel_engine) sel_id = eid;
+ }
+
+ this->sel_engine = sel_id;
+
+ /* make engines first, and then wagons, sorted by ListPositionOfEngine() */
+ _internal_sort_order = false;
+ EngList_Sort(&this->eng_list, TrainEnginesThenWagonsSorter);
+
+ /* and then sort engines */
+ _internal_sort_order = this->descending_sort_order;
+ EngList_SortPartial(&this->eng_list, _sorter[0][this->sort_criteria], 0, num_engines);
+
+ /* and finally sort wagons */
+ EngList_SortPartial(&this->eng_list, _sorter[0][this->sort_criteria], num_engines, num_wagons);
+ }
+
+ /* Generate the list of vehicles */
+ void GenerateBuildList()
+ {
+ if (!this->eng_list.NeedRebuild()) return;
+
+ this->GenerateBuildTrainList();
+ this->eng_list.Compact();
+ this->eng_list.RebuildDone();
+ return; // trains should not reach the last sorting
+
+
+ this->FilterEngineList();
+
+ _internal_sort_order = this->descending_sort_order;
+ EngList_Sort(&this->eng_list, _sorter[this->vehicle_type][this->sort_criteria]);
+
+ this->eng_list.Compact();
+ this->eng_list.RebuildDone();
+ }
+
+ virtual void OnClick(Point pt, int widget, int click_count)
+ {
+ switch (widget) {
+ case WID_BV_SORT_ASCENDING_DESCENDING:
+ this->descending_sort_order ^= true;
+ _last_sort_order[this->vehicle_type] = this->descending_sort_order;
+ this->eng_list.ForceRebuild();
+ this->SetDirty();
+ break;
+
+ case WID_BV_LIST: {
+ uint i = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BV_LIST);
+ size_t num_items = this->eng_list.Length();
+ this->sel_engine = (i < num_items) ? this->eng_list[i] : INVALID_ENGINE;
+ this->SetDirty();
+ if (click_count > 1 && !this->listview_mode) this->OnClick(pt, WID_BV_BUILD, 1);
+ break;
+ }
+ case WID_BV_SORT_DROPDOWN: { // Select sorting criteria dropdown menu
+ uint32 hidden_mask = 0;
+ /* Disable sorting by power or tractive effort when the original acceleration model for road vehicles is being used. */
+ if (this->vehicle_type == VEH_ROAD &&
+ _settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL) {
+ SetBit(hidden_mask, 3); // power
+ SetBit(hidden_mask, 4); // tractive effort
+ SetBit(hidden_mask, 8); // power by running costs
+ }
+ /* Disable sorting by tractive effort when the original acceleration model for trains is being used. */
+ if (this->vehicle_type == VEH_TRAIN &&
+ _settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) {
+ SetBit(hidden_mask, 4); // tractive effort
+ }
+ ShowDropDownMenu(this, _sort_listing[this->vehicle_type], this->sort_criteria, WID_BV_SORT_DROPDOWN, 0, hidden_mask);
+ break;
+ }
+
+ case WID_BV_CARGO_FILTER_DROPDOWN: // Select cargo filtering criteria dropdown menu
+ ShowDropDownMenu(this, this->cargo_filter_texts, this->cargo_filter_criteria, WID_BV_CARGO_FILTER_DROPDOWN, 0, 0);
+ break;
+
+ case WID_BV_BUILD: {
+ EngineID sel_eng = this->sel_engine;
+ if (sel_eng != INVALID_ENGINE) {
+ DoCommandP(0, sel_engine, 0, CMD_BUILD_VIRTUAL_RAIL_VEHICLE, CcAddVirtualEngine);
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Some data on this window has become invalid.
+ * @param data Information about the changed data.
+ * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
+ */
+ virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
+ {
+ if (!gui_scope) return;
+ /* When switching to original acceleration model for road vehicles, clear the selected sort criteria if it is not available now. */
+
+ this->eng_list.ForceRebuild();
+ }
+
+ virtual void SetStringParameters(int widget) const
+ {
+ switch (widget) {
+ case WID_BV_CAPTION:
+ if (this->vehicle_type == VEH_TRAIN && !this->listview_mode) {
+ const RailtypeInfo *rti = GetRailTypeInfo(this->filter.railtype);
+ SetDParam(0, rti->strings.build_caption);
+ } else {
+ SetDParam(0, (this->listview_mode ? STR_VEHICLE_LIST_AVAILABLE_TRAINS : STR_BUY_VEHICLE_TRAIN_ALL_CAPTION) + this->vehicle_type);
+ }
+ break;
+
+ case WID_BV_SORT_DROPDOWN:
+ SetDParam(0, _sort_listing[this->vehicle_type][this->sort_criteria]);
+ break;
+
+ case WID_BV_CARGO_FILTER_DROPDOWN:
+ SetDParam(0, this->cargo_filter_texts[this->cargo_filter_criteria]);
+ break;
+ }
+ }
+
+ virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
+ {
+ switch (widget) {
+ case WID_BV_LIST:
+ resize->height = GetEngineListHeight(this->vehicle_type);
+ size->height = 3 * resize->height;
+ break;
+
+ case WID_BV_PANEL:
+ size->height = this->details_height;
+ break;
+
+ case WID_BV_SORT_ASCENDING_DESCENDING: {
+ Dimension d = GetStringBoundingBox(this->GetWidget(widget)->widget_data);
+ d.width += padding.width + WD_CLOSEBOX_WIDTH * 2; // Doubled since the string is centred and it also looks better.
+ d.height += padding.height;
+ *size = maxdim(*size, d);
+ break;
+ }
+ }
+ }
+
+ virtual void DrawWidget(const Rect &r, int widget) const
+ {
+ switch (widget) {
+ case WID_BV_LIST:
+ DrawEngineList(this->vehicle_type, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP,
+ &this->eng_list, this->vscroll->GetPosition(), min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(),
+ this->eng_list.Length()), this->sel_engine, false, DEFAULT_GROUP);
+ break;
+
+ case WID_BV_SORT_ASCENDING_DESCENDING:
+ this->DrawSortButtonState(WID_BV_SORT_ASCENDING_DESCENDING, this->descending_sort_order ? SBS_DOWN : SBS_UP);
+ break;
+ }
+ }
+
+ virtual void OnPaint()
+ {
+ this->GenerateBuildList();
+ this->vscroll->SetCount(this->eng_list.Length());
+
+ this->DrawWidgets();
+
+ if (!this->IsShaded()) {
+ int needed_height = this->details_height;
+ /* Draw details panels. */
+ if (this->sel_engine != INVALID_ENGINE) {
+ NWidgetBase *nwi = this->GetWidget(WID_BV_PANEL);
+ int text_end = DrawVehiclePurchaseInfo(nwi->pos_x + WD_FRAMETEXT_LEFT, nwi->pos_x + nwi->current_x - WD_FRAMETEXT_RIGHT,
+ nwi->pos_y + WD_FRAMERECT_TOP, this->sel_engine);
+ needed_height = max(needed_height, text_end - (int)nwi->pos_y + WD_FRAMERECT_BOTTOM);
+ }
+ if (needed_height != this->details_height) { // Details window are not high enough, enlarge them.
+ int resize = needed_height - this->details_height;
+ this->details_height = needed_height;
+ this->ReInit(0, resize);
+ return;
+ }
+ }
+ }
+
+ virtual void OnQueryTextFinished(char *str)
+ {
+ if (str == NULL) return;
+
+ DoCommandP(0, this->rename_engine, 0, CMD_RENAME_ENGINE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type), NULL, str);
+ }
+
+ virtual void OnDropdownSelect(int widget, int index)
+ {
+ switch (widget) {
+ case WID_BV_SORT_DROPDOWN:
+ if (this->sort_criteria != index) {
+ this->sort_criteria = index;
+ _last_sort_criteria[this->vehicle_type] = this->sort_criteria;
+ this->eng_list.ForceRebuild();
+ }
+ break;
+
+ case WID_BV_CARGO_FILTER_DROPDOWN: // Select a cargo filter criteria
+ if (this->cargo_filter_criteria != index) {
+ this->cargo_filter_criteria = index;
+ _last_filter_criteria[this->vehicle_type] = this->cargo_filter[this->cargo_filter_criteria];
+ /* deactivate filter if criteria is 'Show All', activate it otherwise */
+ this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
+ this->eng_list.ForceRebuild();
+ }
+ break;
+ }
+ this->SetDirty();
+ }
+
+ virtual void OnResize()
+ {
+ this->vscroll->SetCapacityFromWidget(this, WID_BV_LIST);
+ this->GetWidget(WID_BV_LIST)->widget_data = (this->vscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
+ }
+
+ void AddVirtualEngine(Train *toadd)
+ {
+ if (*virtual_train == NULL) {
+ *virtual_train = toadd;
+ } else {
+ VehicleID target = (*(this->virtual_train))->GetLastUnit()->index;
+
+ DoCommandP(0, (1 << 21) | toadd->index, target, CMD_MOVE_RAIL_VEHICLE);
+ }
+ *noticeParent = true;
+ }
+};
+
+void CcAddVirtualEngine(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
+{
+ if (result.Failed()) return;
+
+ Window* window = FindWindowById(WC_BUILD_VIRTUAL_TRAIN, 0);
+ if (window) {
+ Train* train = Train::From(Vehicle::Get(_new_vehicle_id));
+ ((BuildVirtualTrainWindow*)window)->AddVirtualEngine(train);
+ window->InvalidateData();
+ }
+}
+
+static WindowDesc _build_vehicle_desc(
+ WDP_AUTO, // window position
+ "template create virtual train", // const char* ini_key
+ 240, 268, // window size
+ WC_BUILD_VIRTUAL_TRAIN, // window class
+ WC_CREATE_TEMPLATE, // parent window class
+ WDF_CONSTRUCTION, // window flags
+ _nested_build_vehicle_widgets, lengthof(_nested_build_vehicle_widgets) // widgets + num widgets
+);
+
+void ShowBuildVirtualTrainWindow(Train **vt, bool *noticeParent)
+{
+ // '0' as in VEH_TRAIN = Tile=0
+ assert(IsCompanyBuildableVehicleType(VEH_TRAIN));
+
+ DeleteWindowById(WC_BUILD_VEHICLE, 0);
+
+ new BuildVirtualTrainWindow(&_build_vehicle_desc, vt, noticeParent);
+}
diff --git a/src/tbtr_template_gui_create_virtualtrain.h b/src/tbtr_template_gui_create_virtualtrain.h
new file mode 100644
index 0000000000..ee0b4123c3
--- /dev/null
+++ b/src/tbtr_template_gui_create_virtualtrain.h
@@ -0,0 +1,19 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file tbtr_template_gui_create_virtualtrain.cpp Template-based train replacement: template creation vehicle build GUI header. */
+
+#ifndef BUILD_VIRTUAL_TRAIN_GUI
+#define BUILD_VIRTUAL_TRAIN_GUI
+
+#include "train.h"
+
+void ShowBuildVirtualTrainWindow(Train**, bool*);
+
+#endif
diff --git a/src/tbtr_template_gui_main.cpp b/src/tbtr_template_gui_main.cpp
new file mode 100644
index 0000000000..afd63da94c
--- /dev/null
+++ b/src/tbtr_template_gui_main.cpp
@@ -0,0 +1,803 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file tbtr_template_gui_main.cpp Template-based train replacement: main GUI. */
+
+#include "stdafx.h"
+#include "command_func.h"
+#include "vehicle_gui.h"
+#include "newgrf_engine.h"
+#include "group.h"
+#include "rail.h"
+#include "strings_func.h"
+#include "window_func.h"
+#include "autoreplace_func.h"
+#include "company_func.h"
+#include "engine_base.h"
+#include "window_gui.h"
+#include "viewport_func.h"
+#include "tilehighlight_func.h"
+#include "engine_gui.h"
+#include "settings_func.h"
+#include "core/geometry_func.hpp"
+#include "rail_gui.h"
+#include "network/network.h"
+
+#include "table/sprites.h"
+#include "table/strings.h"
+
+// test creating pool -> creating vehicles
+#include "core/pool_func.hpp"
+
+#include "vehicle_gui_base.h"
+#include "vehicle_base.h"
+#include "train.h"
+#include "vehicle_func.h"
+
+#include "gfx_type.h"
+
+#include "engine_func.h"
+
+// drawing the vehicle length based on occupied tiles
+#include "spritecache.h"
+
+#include "tbtr_template_gui_main.h"
+#include "tbtr_template_gui_create.h"
+#include "tbtr_template_vehicle.h"
+
+#include
+#include
+
+#include "safeguards.h"
+
+
+typedef GUIList GUIGroupList;
+
+enum TemplateReplaceWindowWidgets {
+ TRW_CAPTION,
+
+ TRW_WIDGET_INSET_GROUPS,
+ TRW_WIDGET_TOP_MATRIX,
+ TRW_WIDGET_TOP_SCROLLBAR,
+
+ TRW_WIDGET_INSET_TEMPLATES,
+ TRW_WIDGET_BOTTOM_MATRIX,
+ TRW_WIDGET_MIDDLE_SCROLLBAR,
+ TRW_WIDGET_BOTTOM_SCROLLBAR,
+
+ TRW_WIDGET_TMPL_INFO_INSET,
+ TRW_WIDGET_TMPL_INFO_PANEL,
+
+ TRW_WIDGET_TMPL_PRE_BUTTON_FLUFF,
+
+ TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REUSE,
+ TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_KEEP,
+ TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REFIT,
+ TRW_WIDGET_TMPL_BUTTONS_CONFIG_RIGHTPANEL,
+
+ TRW_WIDGET_TMPL_BUTTONS_DEFINE,
+ TRW_WIDGET_TMPL_BUTTONS_EDIT,
+ TRW_WIDGET_TMPL_BUTTONS_CLONE,
+ TRW_WIDGET_TMPL_BUTTONS_DELETE,
+
+ TRW_WIDGET_TMPL_BUTTONS_EDIT_RIGHTPANEL,
+
+ TRW_WIDGET_TITLE_INFO_GROUP,
+ TRW_WIDGET_TITLE_INFO_TEMPLATE,
+
+ TRW_WIDGET_INFO_GROUP,
+ TRW_WIDGET_INFO_TEMPLATE,
+
+ TRW_WIDGET_TMPL_BUTTONS_SPACER,
+
+ TRW_WIDGET_START,
+ TRW_WIDGET_TRAIN_FLUFF_LEFT,
+ TRW_WIDGET_TRAIN_RAILTYPE_DROPDOWN,
+ TRW_WIDGET_TRAIN_FLUFF_RIGHT,
+ TRW_WIDGET_STOP,
+
+ TRW_WIDGET_SEL_TMPL_DISPLAY_CREATE,
+};
+
+static const NWidgetPart _widgets[] = {
+ // Title bar
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_GREY),
+ NWidget(WWT_CAPTION, COLOUR_GREY, TRW_CAPTION), SetDataTip(STR_TMPL_RPL_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ NWidget(WWT_SHADEBOX, COLOUR_GREY),
+ NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
+ NWidget(WWT_STICKYBOX, COLOUR_GREY),
+ EndContainer(),
+ //Top Matrix
+ NWidget(NWID_VERTICAL),
+ NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_INSET_GROUPS), SetMinimalTextLines(1, WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM), SetResize(1, 0), EndContainer(),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_MATRIX, COLOUR_GREY, TRW_WIDGET_TOP_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetDataTip(0x1, STR_REPLACE_HELP_LEFT_ARRAY), SetResize(1, 0), SetScrollbar(TRW_WIDGET_TOP_SCROLLBAR),
+ NWidget(NWID_VSCROLLBAR, COLOUR_GREY, TRW_WIDGET_TOP_SCROLLBAR),
+ EndContainer(),
+ EndContainer(),
+ // Template Display
+ NWidget(NWID_VERTICAL),
+ NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_INSET_TEMPLATES), SetMinimalTextLines(1, WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM), SetResize(1, 0), EndContainer(),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_MATRIX, COLOUR_GREY, TRW_WIDGET_BOTTOM_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetDataTip(0x1, STR_REPLACE_HELP_RIGHT_ARRAY), SetResize(1, 1), SetScrollbar(TRW_WIDGET_MIDDLE_SCROLLBAR),
+ NWidget(NWID_VSCROLLBAR, COLOUR_GREY, TRW_WIDGET_MIDDLE_SCROLLBAR),
+ EndContainer(),
+ EndContainer(),
+ // Info Area
+ NWidget(NWID_VERTICAL),
+ NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_TMPL_INFO_INSET), SetMinimalTextLines(1, WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM), SetResize(1,0), EndContainer(),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_TMPL_INFO_PANEL), SetMinimalSize(216,120), SetResize(1,0), SetScrollbar(TRW_WIDGET_BOTTOM_SCROLLBAR), EndContainer(),
+ NWidget(NWID_VSCROLLBAR, COLOUR_GREY, TRW_WIDGET_BOTTOM_SCROLLBAR),
+ EndContainer(),
+ EndContainer(),
+ // Control Area
+ NWidget(NWID_VERTICAL),
+ // Spacing
+ NWidget(WWT_INSET, COLOUR_GREY, TRW_WIDGET_TMPL_PRE_BUTTON_FLUFF), SetMinimalSize(139, 12), SetResize(1,0), EndContainer(),
+ // Config buttons
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REUSE), SetMinimalSize(150,12), SetResize(0,0), SetDataTip(STR_TMPL_SET_USEDEPOT, STR_TMPL_SET_USEDEPOT_TIP),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_KEEP), SetMinimalSize(150,12), SetResize(0,0), SetDataTip(STR_TMPL_SET_KEEPREMAINDERS, STR_TMPL_SET_KEEPREMAINDERS_TIP),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REFIT), SetMinimalSize(150,12), SetResize(0,0), SetDataTip(STR_TMPL_SET_REFIT, STR_TMPL_SET_REFIT_TIP),
+ NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_CONFIG_RIGHTPANEL), SetMinimalSize(12,12), SetResize(1,0), EndContainer(),
+ EndContainer(),
+ // Edit buttons
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_DEFINE), SetMinimalSize(75,12), SetResize(0,0), SetDataTip(STR_TMPL_DEFINE_TEMPLATE, STR_TMPL_DEFINE_TEMPLATE),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_EDIT), SetMinimalSize(75,12), SetResize(0,0), SetDataTip(STR_TMPL_EDIT_TEMPLATE, STR_TMPL_EDIT_TEMPLATE),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_CLONE), SetMinimalSize(75,12), SetResize(0,0), SetDataTip(STR_TMPL_CREATE_CLONE_VEH, STR_TMPL_CREATE_CLONE_VEH),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_DELETE), SetMinimalSize(75,12), SetResize(0,0), SetDataTip(STR_TMPL_DELETE_TEMPLATE, STR_TMPL_DELETE_TEMPLATE),
+ NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_TMPL_BUTTONS_EDIT_RIGHTPANEL), SetMinimalSize(50,12), SetResize(1,0), EndContainer(),
+ EndContainer(),
+ EndContainer(),
+ // Start/Stop buttons
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_START), SetMinimalSize(150, 12), SetDataTip(STR_TMPL_RPL_START, STR_REPLACE_ENGINE_WAGON_SELECT_HELP),
+ NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_TRAIN_FLUFF_LEFT), SetMinimalSize(15, 12), EndContainer(),
+ NWidget(WWT_DROPDOWN, COLOUR_GREY, TRW_WIDGET_TRAIN_RAILTYPE_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(0x0, STR_REPLACE_HELP_RAILTYPE), SetResize(1, 0),
+ NWidget(WWT_PANEL, COLOUR_GREY, TRW_WIDGET_TRAIN_FLUFF_RIGHT), SetMinimalSize(16, 12), EndContainer(),
+ NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, TRW_WIDGET_STOP), SetMinimalSize(150, 12), SetDataTip(STR_TMPL_RPL_STOP, STR_REPLACE_REMOVE_WAGON_HELP),
+ NWidget(WWT_RESIZEBOX, COLOUR_GREY),
+ EndContainer(),
+};
+
+static WindowDesc _replace_rail_vehicle_desc(
+ WDP_AUTO,
+ "template replace window",
+ 456, 156,
+ WC_TEMPLATEGUI_MAIN,
+ WC_NONE, // parent window class
+ WDF_CONSTRUCTION,
+ _widgets, lengthof(_widgets)
+);
+
+class TemplateReplaceWindow : public Window {
+private:
+
+ GUIGroupList groups; ///< List of groups
+ byte unitnumber_digits;
+
+ SmallVector indents; ///< Indentation levels
+
+ short line_height;
+ short matrixContentLeftMargin;
+
+ int details_height; ///< Minimal needed height of the details panels (found so far).
+ RailType sel_railtype; ///< Type of rail tracks selected.
+ Scrollbar *vscroll[3];
+ // listing/sorting continued
+ GUITemplateList templates;
+ GUITemplateList::SortFunction **template_sorter_funcs;
+
+ short selected_template_index;
+ short selected_group_index;
+
+ bool templateNotice;
+ bool editInProgress;
+
+public:
+ TemplateReplaceWindow(WindowDesc *wdesc, byte dig, int step_h) : Window(wdesc)
+ {
+ // listing/sorting
+ templates.SetSortFuncs(this->template_sorter_funcs);
+
+ // From BaseVehicleListWindow
+ this->unitnumber_digits = dig;
+
+ this->sel_railtype = RAILTYPE_BEGIN;
+ this->details_height = 10 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
+
+ this->line_height = step_h;
+
+ this->CreateNestedTree(wdesc != NULL);
+ this->vscroll[0] = this->GetScrollbar(TRW_WIDGET_TOP_SCROLLBAR);
+ this->vscroll[1] = this->GetScrollbar(TRW_WIDGET_MIDDLE_SCROLLBAR);
+ this->vscroll[2] = this->GetScrollbar(TRW_WIDGET_BOTTOM_SCROLLBAR);
+ this->FinishInitNested(VEH_TRAIN);
+
+ this->owner = _local_company;
+
+ this->groups.ForceRebuild();
+ this->groups.NeedResort();
+ this->BuildGroupList(_local_company);
+
+ this->matrixContentLeftMargin = 40;
+ this->selected_template_index = -1;
+ this->selected_group_index = -1;
+
+ this->UpdateButtonState();
+
+ this->templateNotice = false;
+ this->editInProgress = false;
+
+ this->templates.ForceRebuild();
+
+ BuildTemplateGuiList(&this->templates, this->vscroll[1], this->owner, this->sel_railtype);
+ }
+
+ ~TemplateReplaceWindow() {
+ DeleteWindowById(WC_CREATE_TEMPLATE, this->window_number);
+ }
+
+ virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
+ {
+ switch (widget) {
+ case TRW_WIDGET_TOP_MATRIX:
+ resize->height = GetVehicleListHeight(VEH_TRAIN, FONT_HEIGHT_NORMAL + WD_MATRIX_TOP) / 2;
+ size->height = 8 * resize->height;
+ break;
+ case TRW_WIDGET_BOTTOM_MATRIX:
+ resize->height = GetVehicleListHeight(VEH_TRAIN, FONT_HEIGHT_NORMAL + WD_MATRIX_TOP);
+ size->height = 4 * resize->height;
+ break;
+ case TRW_WIDGET_TRAIN_RAILTYPE_DROPDOWN: {
+ Dimension d = {0, 0};
+ for (RailType rt = RAILTYPE_BEGIN; rt != RAILTYPE_END; rt++) {
+ const RailtypeInfo *rti = GetRailTypeInfo(rt);
+ // Skip rail type if it has no label
+ if (rti->label == 0) continue;
+ d = maxdim(d, GetStringBoundingBox(rti->strings.replace_text));
+ }
+ d.width += padding.width;
+ d.height += padding.height;
+ *size = maxdim(*size, d);
+ break;
+ }
+ }
+ }
+
+ virtual void SetStringParameters(int widget) const
+ {
+ switch (widget) {
+ case TRW_CAPTION:
+ SetDParam(0, STR_TMPL_RPL_TITLE);
+ break;
+ }
+ }
+
+ virtual void DrawWidget(const Rect &r, int widget) const
+ {
+ switch (widget) {
+ case TRW_WIDGET_TOP_MATRIX: {
+ DrawAllGroupsFunction(this->line_height, r);
+ break;
+ }
+ case TRW_WIDGET_BOTTOM_MATRIX: {
+ DrawTemplateList(this->line_height, r);
+ break;
+ }
+ case TRW_WIDGET_TMPL_INFO_PANEL: {
+ DrawTemplateInfo(this->line_height, r);
+ break;
+ }
+ case TRW_WIDGET_INSET_GROUPS: {
+ DrawString(r.left + 2, r.right - 2, r.top + 2, STR_TMPL_MAINGUI_DEFINEDGROUPS);
+ break;
+ }
+ case TRW_WIDGET_INSET_TEMPLATES: {
+ DrawString(r.left + 2, r.right - 2, r.top + 2, STR_TMPL_AVAILABLE_TEMPLATES);
+ break;
+ }
+ case TRW_WIDGET_TMPL_INFO_INSET: {
+ DrawString(r.left + 2, r.right - 2, r.top + 2, STR_TMPL_TEMPLATE_INFO);
+ break;
+ }
+ }
+ }
+
+ virtual void OnPaint()
+ {
+ BuildTemplateGuiList(&this->templates, this->vscroll[1], this->owner, this->sel_railtype);
+
+ this->BuildGroupList(_local_company);
+
+ if (templateNotice) {
+ BuildTemplateGuiList(&this->templates, vscroll[1], _local_company, this->sel_railtype);
+ templateNotice = false;
+ this->SetDirty();
+ }
+ /* sets the colour of that art thing */
+ this->GetWidget(TRW_WIDGET_TRAIN_FLUFF_LEFT)->colour = _company_colours[_local_company];
+ this->GetWidget(TRW_WIDGET_TRAIN_FLUFF_RIGHT)->colour = _company_colours[_local_company];
+
+ /* Show the selected railtype in the pulldown menu */
+ this->GetWidget(TRW_WIDGET_TRAIN_RAILTYPE_DROPDOWN)->widget_data = GetRailTypeInfo(sel_railtype)->strings.replace_text;
+
+ if ((this->selected_template_index < 0) || (this->selected_template_index >= (short)this->templates.Length())) {
+ this->vscroll[2]->SetCount(24);
+ } else {
+ const TemplateVehicle *tmp = this->templates[this->selected_template_index];
+ uint min_height = 30;
+ uint height = 30;
+ CargoArray cargo_caps;
+ short count_columns = 0;
+ short max_columns = 2;
+
+ for (; tmp != NULL; tmp = tmp->Next()) {
+ cargo_caps[tmp->cargo_type] += tmp->cargo_cap;
+ }
+
+ for (CargoID i = 0; i < NUM_CARGO; ++i) {
+ if (cargo_caps[i] > 0) {
+ if (count_columns % max_columns == 0) {
+ height += this->line_height / 3;
+ }
+
+ ++count_columns;
+ }
+ }
+
+ min_height = max(min_height, height);
+ this->vscroll[2]->SetCount(min_height);
+ }
+
+ this->DrawWidgets();
+ }
+
+ virtual void OnClick(Point pt, int widget, int click_count)
+ {
+ if (this->editInProgress) return;
+
+ switch (widget) {
+ case TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REUSE: {
+ if ((this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length())) {
+ uint32 template_index = ((this->templates)[selected_template_index])->index;
+
+ DoCommandP(0, template_index, 0, CMD_TOGGLE_REUSE_DEPOT_VEHICLES, NULL);
+ }
+ break;
+ }
+ case TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_KEEP: {
+ if ((this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length())) {
+ uint32 template_index = ((this->templates)[selected_template_index])->index;
+
+ DoCommandP(0, template_index, 0, CMD_TOGGLE_KEEP_REMAINING_VEHICLES, NULL);
+ }
+ break;
+ }
+ case TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REFIT: {
+ if ((this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length())) {
+ uint32 template_index = ((this->templates)[selected_template_index])->index;
+
+ DoCommandP(0, template_index, 0, CMD_TOGGLE_REFIT_AS_TEMPLATE, NULL);
+ }
+ break;
+ }
+ case TRW_WIDGET_TMPL_BUTTONS_DEFINE: {
+ ShowTemplateCreateWindow(0, &templateNotice, &editInProgress, this->line_height);
+ break;
+ }
+ case TRW_WIDGET_TMPL_BUTTONS_EDIT: {
+ if ((this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length())) {
+ editInProgress = true;
+ TemplateVehicle *sel = TemplateVehicle::Get(((this->templates)[selected_template_index])->index);
+ ShowTemplateCreateWindow(sel, &templateNotice, &editInProgress, this->line_height);
+ }
+ break;
+ }
+ case TRW_WIDGET_TMPL_BUTTONS_CLONE: {
+ this->SetWidgetDirty(TRW_WIDGET_TMPL_BUTTONS_CLONE);
+ this->ToggleWidgetLoweredState(TRW_WIDGET_TMPL_BUTTONS_CLONE);
+
+ if (this->IsWidgetLowered(TRW_WIDGET_TMPL_BUTTONS_CLONE)) {
+ static const CursorID clone_icon = SPR_CURSOR_CLONE_TRAIN;
+ SetObjectToPlaceWnd(clone_icon, PAL_NONE, HT_VEHICLE, this);
+ } else {
+ ResetObjectToPlace();
+ }
+ break;
+ }
+ case TRW_WIDGET_TMPL_BUTTONS_DELETE:
+ if ((this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length()) && !editInProgress) {
+
+ uint32 template_index = ((this->templates)[selected_template_index])->index;
+
+ bool succeeded = DoCommandP(0, template_index, 0, CMD_DELETE_TEMPLATE_VEHICLE, NULL);
+
+ if (succeeded) {
+ BuildTemplateGuiList(&this->templates, this->vscroll[1], this->owner, this->sel_railtype);
+ selected_template_index = -1;
+ }
+ }
+ break;
+ case TRW_WIDGET_TRAIN_RAILTYPE_DROPDOWN: // Railtype selection dropdown menu
+ ShowDropDownList(this, GetRailTypeDropDownList(true), sel_railtype, TRW_WIDGET_TRAIN_RAILTYPE_DROPDOWN);
+ break;
+ case TRW_WIDGET_TOP_MATRIX: {
+ uint16 newindex = (uint16)((pt.y - this->nested_array[TRW_WIDGET_TOP_MATRIX]->pos_y) / (this->line_height / 2) ) + this->vscroll[0]->GetPosition();
+ if (newindex == this->selected_group_index || newindex >= this->groups.Length()) {
+ this->selected_group_index = -1;
+ } else if (newindex < this->groups.Length()) {
+ this->selected_group_index = newindex;
+ }
+ this->UpdateButtonState();
+ break;
+ }
+ case TRW_WIDGET_BOTTOM_MATRIX: {
+ uint16 newindex = (uint16)((pt.y - this->nested_array[TRW_WIDGET_BOTTOM_MATRIX]->pos_y) / this->line_height) + this->vscroll[1]->GetPosition();
+ if (newindex == this->selected_template_index || newindex >= templates.Length()) {
+ this->selected_template_index = -1;
+ } else if (newindex < templates.Length()) {
+ this->selected_template_index = newindex;
+ }
+ this->UpdateButtonState();
+ break;
+ }
+ case TRW_WIDGET_START: {
+ if ((this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length()) &&
+ (this->selected_group_index >= 0) && (this->selected_group_index < (short)this->groups.Length())) {
+ uint32 tv_index = ((this->templates)[selected_template_index])->index;
+ int current_group_index = (this->groups)[this->selected_group_index]->index;
+
+ DoCommandP(0, current_group_index, tv_index, CMD_ISSUE_TEMPLATE_REPLACEMENT, NULL);
+ this->UpdateButtonState();
+ }
+ break;
+ }
+ case TRW_WIDGET_STOP:
+ if ((this->selected_group_index < 0) || (this->selected_group_index >= (short)this->groups.Length())) {
+ return;
+ }
+
+ int current_group_index = (this->groups)[this->selected_group_index]->index;
+
+ DoCommandP(0, current_group_index, 0, CMD_DELETE_TEMPLATE_REPLACEMENT, NULL);
+ this->UpdateButtonState();
+ break;
+ }
+ this->SetDirty();
+ }
+
+ virtual bool OnVehicleSelect(const Vehicle *v)
+ {
+ bool succeeded = DoCommandP(0, v->index, 0, CMD_CLONE_TEMPLATE_VEHICLE_FROM_TRAIN, NULL);
+
+ if (!succeeded) return false;
+
+ BuildTemplateGuiList(&this->templates, vscroll[1], _local_company, this->sel_railtype);
+ this->ToggleWidgetLoweredState(TRW_WIDGET_TMPL_BUTTONS_CLONE);
+ ResetObjectToPlace();
+ this->SetDirty();
+
+ return true;
+ }
+
+ virtual void OnDropdownSelect(int widget, int index)
+ {
+ RailType temp = (RailType) index;
+ if (temp == this->sel_railtype) return; // we didn't select a new one. No need to change anything
+ this->sel_railtype = temp;
+ /* Reset scrollbar positions */
+ this->vscroll[0]->SetPosition(0);
+ this->vscroll[1]->SetPosition(0);
+ BuildTemplateGuiList(&this->templates, this->vscroll[1], this->owner, this->sel_railtype);
+ this->SetDirty();
+ }
+
+ virtual void OnResize()
+ {
+ /* Top Matrix */
+ NWidgetCore *nwi = this->GetWidget(TRW_WIDGET_TOP_MATRIX);
+ this->vscroll[0]->SetCapacityFromWidget(this, TRW_WIDGET_TOP_MATRIX);
+ nwi->widget_data = (this->vscroll[0]->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
+ /* Bottom Matrix */
+ NWidgetCore *nwi2 = this->GetWidget(TRW_WIDGET_BOTTOM_MATRIX);
+ this->vscroll[1]->SetCapacityFromWidget(this, TRW_WIDGET_BOTTOM_MATRIX);
+ nwi2->widget_data = (this->vscroll[1]->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
+ /* Info panel */
+ NWidgetCore *nwi3 = this->GetWidget(TRW_WIDGET_TMPL_INFO_PANEL);
+ this->vscroll[2]->SetCapacity(nwi3->current_y);
+ }
+
+ virtual void OnTick()
+ {
+ if (templateNotice) {
+ BuildTemplateGuiList(&this->templates, this->vscroll[1], this->owner, this->sel_railtype);
+ this->SetDirty();
+ templateNotice = false;
+ }
+
+ }
+
+ virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
+ {
+ this->groups.ForceRebuild();
+ this->templates.ForceRebuild();
+ this->UpdateButtonState();
+ }
+
+ /** For a given group (id) find the template that is issued for template replacement for this group and return this template's index
+ * from the gui list */
+ short FindTemplateIndexForGroup(short gid) const
+ {
+ TemplateReplacement *tr = GetTemplateReplacementByGroupID(gid);
+ if (!tr) return -1;
+
+ for (uint32 i = 0; i < this->templates.Length(); ++i) {
+ if (templates[i]->index == tr->sel_template) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ void AddParents(GUIGroupList *source, GroupID parent, int indent)
+ {
+ for (const Group **g = source->Begin(); g != source->End(); g++) {
+ if ((*g)->parent == parent) {
+ *this->groups.Append() = *g;
+ *this->indents.Append() = indent;
+ AddParents(source, (*g)->index, indent + 1);
+ }
+ }
+ }
+
+ /** Sort the groups by their name */
+ static int CDECL GroupNameSorter(const Group * const *a, const Group * const *b)
+ {
+ static const Group *last_group[2] = { NULL, NULL };
+ static char last_name[2][64] = { "", "" };
+
+ if (*a != last_group[0]) {
+ last_group[0] = *a;
+ SetDParam(0, (*a)->index);
+ GetString(last_name[0], STR_GROUP_NAME, lastof(last_name[0]));
+ }
+
+ if (*b != last_group[1]) {
+ last_group[1] = *b;
+ SetDParam(0, (*b)->index);
+ GetString(last_name[1], STR_GROUP_NAME, lastof(last_name[1]));
+ }
+
+ int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
+ if (r == 0) return (*a)->index - (*b)->index;
+ return r;
+ }
+
+ void BuildGroupList(Owner owner)
+ {
+ if (!this->groups.NeedRebuild()) return;
+
+ this->groups.Clear();
+ this->indents.Clear();
+
+ GUIGroupList list;
+
+ const Group *g;
+ FOR_ALL_GROUPS(g) {
+ if (g->owner == owner && g->vehicle_type == VEH_TRAIN) {
+ *list.Append() = g;
+ }
+ }
+
+ list.ForceResort();
+ list.Sort(&GroupNameSorter);
+
+ AddParents(&list, INVALID_GROUP, 0);
+
+ this->groups.Compact();
+ this->groups.RebuildDone();
+ this->vscroll[0]->SetCount(groups.Length());
+ }
+
+ void DrawAllGroupsFunction(int line_height, const Rect &r) const
+ {
+ int left = r.left + WD_MATRIX_LEFT;
+ int right = r.right - WD_MATRIX_RIGHT;
+ int y = r.top;
+ int max = min(this->vscroll[0]->GetPosition() + this->vscroll[0]->GetCapacity(), this->groups.Length());
+
+ /* Then treat all groups defined by/for the current company */
+ for (int i = this->vscroll[0]->GetPosition(); i < max; ++i) {
+ const Group *g = (this->groups)[i];
+ short g_id = g->index;
+
+ /* Fill the background of the current cell in a darker tone for the currently selected template */
+ if (this->selected_group_index == i) {
+ GfxFillRect(left, y, right, y+(this->line_height) / 2, _colour_gradient[COLOUR_GREY][3]);
+ }
+
+ SetDParam(0, g_id);
+ StringID str = STR_GROUP_NAME;
+ DrawString(left + 30 + this->indents[i] * 10, right, y + 2, str, TC_BLACK);
+
+ /* Draw the template in use for this group, if there is one */
+ short template_in_use = FindTemplateIndexForGroup(g_id);
+ if (template_in_use >= 0) {
+ SetDParam(0, template_in_use);
+ DrawString (left, right, y + 2, STR_TMPL_GROUP_USES_TEMPLATE, TC_BLACK, SA_HOR_CENTER);
+ } else if (GetTemplateReplacementByGroupID(g_id)) { /* If there isn't a template applied from the current group, check if there is one for another rail type */
+ DrawString (left, right, y + 2, STR_TMPL_TMPLRPL_EX_DIFF_RAILTYPE, TC_SILVER, SA_HOR_CENTER);
+ }
+
+ /* Draw the number of trains that still need to be treated by the currently selected template replacement */
+ TemplateReplacement *tr = GetTemplateReplacementByGroupID(g_id);
+ if (tr) {
+ TemplateVehicle *tv = TemplateVehicle::Get(tr->sel_template);
+ int num_trains = NumTrainsNeedTemplateReplacement(g_id, tv);
+ // Draw text
+ TextColour color = TC_GREY;
+ if (num_trains) color = TC_BLACK;
+ DrawString(left, right - 16, y + 2, STR_TMPL_NUM_TRAINS_NEED_RPL, color, SA_RIGHT);
+ // Draw number
+ if (num_trains ) {
+ color = TC_ORANGE;
+ } else {
+ color = TC_GREY;
+ }
+ SetDParam(0, num_trains);
+ DrawString(left, right - 4, y + 2, STR_JUST_INT, color, SA_RIGHT);
+ }
+
+ y += line_height / 2;
+ }
+ }
+
+ void DrawTemplateList(int line_height, const Rect &r) const
+ {
+ int left = r.left;
+ int right = r.right;
+ int y = r.top;
+
+ Scrollbar *draw_vscroll = vscroll[1];
+ uint max = min(draw_vscroll->GetPosition() + draw_vscroll->GetCapacity(), this->templates.Length());
+
+ const TemplateVehicle *v;
+ for (uint i = draw_vscroll->GetPosition(); i < max; ++i) {
+ v = (this->templates)[i];
+
+ /* Fill the background of the current cell in a darker tone for the currently selected template */
+ if (this->selected_template_index == (int32) i) {
+ GfxFillRect(left, y, right, y + this->line_height, _colour_gradient[COLOUR_GREY][3]);
+ }
+
+ /* Draw a notification string for chains that are not runnable */
+ if (v->IsFreeWagonChain()) {
+ DrawString(left, right - 2, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 2, STR_TMPL_WARNING_FREE_WAGON, TC_RED, SA_RIGHT);
+ }
+
+ /* Draw the template's length in tile-units */
+ SetDParam(0, v->GetRealLength());
+ SetDParam(1, 1);
+ DrawString(left, right - 4, y + 2, STR_TINY_BLACK_DECIMAL, TC_BLACK, SA_RIGHT);
+
+ /* Draw the template */
+ DrawTemplate(v, left + 50, right, y);
+
+ /* Buying cost */
+ SetDParam(0, CalculateOverallTemplateCost(v));
+ DrawString(left + 35, right, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 2, STR_TMPL_TEMPLATE_OVR_VALUE_notinyfont, TC_BLUE, SA_LEFT);
+
+ /* Index of current template vehicle in the list of all templates for its company */
+ SetDParam(0, i);
+ DrawString(left + 5, left + 25, y + 2, STR_BLACK_INT, TC_BLACK, SA_RIGHT);
+
+ /* Draw whether the current template is in use by any group */
+ if (v->NumGroupsUsingTemplate() > 0) {
+ DrawString(left + 35, right, y + line_height - FONT_HEIGHT_SMALL * 2 - 4 - WD_FRAMERECT_BOTTOM - 2, STR_TMP_TEMPLATE_IN_USE, TC_GREEN, SA_LEFT);
+ }
+
+ /* Draw information about template configuration settings */
+ TextColour color;
+
+ color = v->IsSetReuseDepotVehicles() ? TC_LIGHT_BLUE : TC_GREY;
+ DrawString(right - 225, right, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 2, STR_TMPL_CONFIG_USEDEPOT, color, SA_LEFT);
+
+ color = v->IsSetKeepRemainingVehicles() ? TC_LIGHT_BLUE : TC_GREY;
+ DrawString(right - 150, right, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 2, STR_TMPL_CONFIG_KEEPREMAINDERS, color, SA_LEFT);
+
+ color = v->IsSetRefitAsTemplate() ? TC_LIGHT_BLUE : TC_GREY;
+ DrawString(right - 75, right, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 2, STR_TMPL_CONFIG_REFIT, color, SA_LEFT);
+
+ y += line_height;
+ }
+ }
+
+ void DrawTemplateInfo(int line_height, const Rect &r) const
+ {
+ if ((this->selected_template_index < 0) || (this->selected_template_index >= (short)this->templates.Length())) {
+ return;
+ }
+
+ DrawPixelInfo tmp_dpi, *old_dpi;
+
+ if (!FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.right - r.left, r.bottom - r.top)) {
+ return;
+ }
+
+ old_dpi = _cur_dpi;
+ _cur_dpi = &tmp_dpi;
+
+ const TemplateVehicle *tmp = this->templates[this->selected_template_index];
+
+ /* Draw vehicle performance info */
+ SetDParam(2, tmp->max_speed);
+ SetDParam(1, tmp->power);
+ SetDParam(0, tmp->weight);
+ SetDParam(3, tmp->max_te);
+ DrawString(8, r.right, 4 - this->vscroll[2]->GetPosition(), STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE);
+
+ /* Draw cargo summary */
+ short top = 30 - this->vscroll[2]->GetPosition();
+ short left = 8;
+ short count_columns = 0;
+ short max_columns = 2;
+
+ CargoArray cargo_caps;
+ for (; tmp != NULL; tmp = tmp->Next()) {
+ cargo_caps[tmp->cargo_type] += tmp->cargo_cap;
+ }
+ int x = left;
+ for (CargoID i = 0; i < NUM_CARGO; ++i) {
+ if (cargo_caps[i] > 0) {
+ count_columns++;
+ SetDParam(0, i);
+ SetDParam(1, cargo_caps[i]);
+ SetDParam(2, _settings_game.vehicle.freight_trains);
+ DrawString(x, r.right, top, FreightWagonMult(i) > 1 ? STR_TMPL_CARGO_SUMMARY_MULTI : STR_TMPL_CARGO_SUMMARY, TC_LIGHT_BLUE, SA_LEFT);
+ x += 250;
+ if (count_columns % max_columns == 0) {
+ x = left;
+ top += this->line_height / 3;
+ }
+ }
+ }
+
+ _cur_dpi = old_dpi;
+ }
+
+ void UpdateButtonState()
+ {
+ bool selected_ok = (this->selected_template_index >= 0) && (this->selected_template_index < (short)this->templates.Length());
+ bool group_ok = (this->selected_group_index >= 0) && (this->selected_group_index < (short)this->groups.Length());
+
+ short g_id = -1;
+ if (group_ok) {
+ const Group *g = (this->groups)[this->selected_group_index];
+ g_id = g->index;
+ }
+
+ this->SetWidgetDisabledState(TRW_WIDGET_TMPL_BUTTONS_EDIT, !selected_ok);
+ this->SetWidgetDisabledState(TRW_WIDGET_TMPL_BUTTONS_DELETE, !selected_ok);
+ this->SetWidgetDisabledState(TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REUSE, !selected_ok);
+ this->SetWidgetDisabledState(TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_KEEP, !selected_ok);
+ this->SetWidgetDisabledState(TRW_WIDGET_TMPL_BUTTONS_CONFIGTMPL_REFIT, !selected_ok);
+
+ this->SetWidgetDisabledState(TRW_WIDGET_START, !(selected_ok && group_ok && FindTemplateIndexForGroup(g_id) != this->selected_template_index));
+ this->SetWidgetDisabledState(TRW_WIDGET_STOP, !(group_ok && GetTemplateReplacementByGroupID(g_id) != NULL));
+ }
+};
+
+void ShowTemplateReplaceWindow(byte dig, int step_h)
+{
+ new TemplateReplaceWindow(&_replace_rail_vehicle_desc, dig, step_h);
+}
diff --git a/src/tbtr_template_gui_main.h b/src/tbtr_template_gui_main.h
new file mode 100644
index 0000000000..0a42c6e8c4
--- /dev/null
+++ b/src/tbtr_template_gui_main.h
@@ -0,0 +1,28 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file tbtr_template_gui_main.h Template-based train replacement: main GUI header. */
+
+#ifndef TEMPLATE_GUI_H
+#define TEMPLATE_GUI_H
+
+#include "engine_type.h"
+#include "group_type.h"
+#include "vehicle_type.h"
+#include "string_func.h"
+#include "strings_func.h"
+
+#include "tbtr_template_vehicle.h"
+#include "tbtr_template_vehicle_func.h"
+
+typedef GUIList GUIGroupList;
+
+void ShowTemplateReplaceWindow(byte, int);
+
+#endif
diff --git a/src/tbtr_template_vehicle.cpp b/src/tbtr_template_vehicle.cpp
new file mode 100644
index 0000000000..1a87266f0d
--- /dev/null
+++ b/src/tbtr_template_vehicle.cpp
@@ -0,0 +1,214 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file tbtr_template_vehicle.cpp Template-based train replacement: template vehicle. */
+
+#include "stdafx.h"
+#include "company_func.h"
+#include "train.h"
+#include "command_func.h"
+#include "engine_func.h"
+#include "vehicle_func.h"
+#include "autoreplace_func.h"
+#include "autoreplace_gui.h"
+#include "group.h"
+#include "articulated_vehicles.h"
+#include "core/random_func.hpp"
+#include "core/pool_type.hpp"
+#include "engine_type.h"
+#include "group_type.h"
+#include "core/pool_func.hpp"
+
+#include "table/strings.h"
+
+#include "newgrf.h"
+
+#include "vehicle_type.h"
+#include "vehicle_base.h"
+#include "vehicle_func.h"
+
+#include "table/train_cmd.h"
+
+#include "tbtr_template_vehicle.h"
+
+// since doing stuff with sprites
+#include "newgrf_spritegroup.h"
+#include "newgrf_engine.h"
+#include "newgrf_cargo.h"
+
+#include "safeguards.h"
+
+TemplatePool _template_pool("TemplatePool");
+INSTANTIATE_POOL_METHODS(Template)
+
+TemplateReplacementPool _template_replacement_pool("TemplateReplacementPool");
+INSTANTIATE_POOL_METHODS(TemplateReplacement)
+
+
+TemplateVehicle::TemplateVehicle(VehicleType ty, EngineID eid, byte subtypeflag, Owner current_owner)
+{
+ this->type = ty;
+ this->engine_type = eid;
+
+ this->reuse_depot_vehicles = true;
+ this->keep_remaining_vehicles = true;
+
+ this->first = this;
+ this->next = 0x0;
+ this->previous = 0x0;
+ this->owner_b = _current_company;
+
+ this->cur_image = SPR_IMG_QUERY;
+
+ this->owner = current_owner;
+
+ this->real_consist_length = 0;
+}
+
+TemplateVehicle::~TemplateVehicle() {
+ TemplateVehicle *v = this->Next();
+ this->SetNext(NULL);
+
+ delete v;
+}
+
+/** getting */
+void TemplateVehicle::SetNext(TemplateVehicle *v) { this->next = v; }
+void TemplateVehicle::SetPrev(TemplateVehicle *v) { this->previous = v; }
+void TemplateVehicle::SetFirst(TemplateVehicle *v) { this->first = v; }
+
+TemplateVehicle* TemplateVehicle::GetNextUnit() const
+{
+ TemplateVehicle *tv = this->Next();
+ while (tv && HasBit(tv->subtype, GVSF_ARTICULATED_PART)) {
+ tv = tv->Next();
+ }
+ if (tv && HasBit(tv->subtype, GVSF_MULTIHEADED) && !HasBit(tv->subtype, GVSF_ENGINE)) tv = tv->Next();
+ return tv;
+}
+
+TemplateVehicle* TemplateVehicle::GetPrevUnit()
+{
+ TemplateVehicle *tv = this->Prev();
+ while (tv && HasBit(tv->subtype, GVSF_ARTICULATED_PART|GVSF_ENGINE)) {
+ tv = tv->Prev();
+ }
+ if (tv && HasBit(tv->subtype, GVSF_MULTIHEADED|GVSF_ENGINE)) tv = tv->Prev();
+ return tv;
+}
+
+/** setting */
+void appendTemplateVehicle(TemplateVehicle *orig, TemplateVehicle *newv)
+{
+ if (!orig) return;
+ while (orig->Next()) orig = orig->Next();
+ orig->SetNext(newv);
+ newv->SetPrev(orig);
+ newv->SetFirst(orig->First());
+}
+
+void insertTemplateVehicle(TemplateVehicle *orig, TemplateVehicle *newv, TemplateVehicle *insert_after)
+{
+ if (!orig || !insert_after) return;
+ TemplateVehicle *insert_before = insert_after->Next();
+ insert_after->SetNext(newv);
+ insert_before->SetPrev(newv);
+ newv->SetPrev(insert_after);
+ newv->SetNext(insert_before);
+ newv->SetFirst(insert_after);
+}
+
+/** Length()
+ * @return: length of vehicle, including current part
+ */
+int TemplateVehicle::Length() const
+{
+ int l = 1;
+ const TemplateVehicle *tmp = this;
+ while (tmp->Next()) {
+ tmp = tmp->Next();
+ l++;
+ }
+ return l;
+}
+
+TemplateReplacement* GetTemplateReplacementByGroupID(GroupID gid)
+{
+ TemplateReplacement *tr;
+ FOR_ALL_TEMPLATE_REPLACEMENTS(tr) {
+ if (tr->Group() == gid) {
+ return tr;
+ }
+ }
+ return NULL;
+}
+
+TemplateReplacement* GetTemplateReplacementByTemplateID(TemplateID tid)
+{
+ TemplateReplacement *tr;
+ FOR_ALL_TEMPLATE_REPLACEMENTS(tr) {
+ if (tr->Template() == tid) {
+ return tr;
+ }
+ }
+ return NULL;
+}
+
+bool IssueTemplateReplacement(GroupID gid, TemplateID tid)
+{
+ TemplateReplacement *tr = GetTemplateReplacementByGroupID(gid);
+
+ if (tr) {
+ /* Then set the new TemplateVehicle and return */
+ tr->SetTemplate(tid);
+ return true;
+ } else if (TemplateReplacement::CanAllocateItem()) {
+ tr = new TemplateReplacement(gid, tid);
+ return true;
+ }
+
+ else return false;
+}
+
+short TemplateVehicle::NumGroupsUsingTemplate() const
+{
+ short amount = 0;
+ const TemplateReplacement *tr;
+ FOR_ALL_TEMPLATE_REPLACEMENTS(tr) {
+ if (tr->sel_template == this->index) {
+ amount++;
+ }
+ }
+ return amount;
+}
+
+short TemplateVehicle::CountEnginesInChain()
+{
+ TemplateVehicle *tv = this->first;
+ short count = 0;
+ for (; tv != NULL; tv = tv->GetNextUnit()) {
+ if (HasBit(tv->subtype, GVSF_ENGINE)) {
+ count++;
+ }
+ }
+ return count;
+}
+
+short deleteIllegalTemplateReplacements(GroupID g_id)
+{
+ short del_amount = 0;
+ const TemplateReplacement *tr;
+ FOR_ALL_TEMPLATE_REPLACEMENTS(tr) {
+ if (tr->group == g_id) {
+ delete tr;
+ del_amount++;
+ }
+ }
+ return del_amount;
+}
diff --git a/src/tbtr_template_vehicle.h b/src/tbtr_template_vehicle.h
new file mode 100644
index 0000000000..0631f85b8d
--- /dev/null
+++ b/src/tbtr_template_vehicle.h
@@ -0,0 +1,208 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file tbtr_template_vehicle.h Template-based train replacement: template vehicle header. */
+
+#ifndef TEMPLATE_VEH_H
+#define TEMPLATE_VEH_H
+
+#include "company_func.h"
+
+#include "vehicle_type.h"
+#include "vehicle_base.h"
+#include "vehicle_func.h"
+
+#include "articulated_vehicles.h"
+#include "newgrf_callbacks.h"
+#include "newgrf_engine.h"
+#include "newgrf_spritegroup.h"
+
+#include "engine_base.h"
+#include "engine_type.h"
+#include "engine_func.h"
+
+#include "sortlist_type.h"
+
+#define FOR_ALL_TEMPLATES_FROM(var, start) FOR_ALL_ITEMS_FROM(TemplateVehicle, template_index, var, start)
+#define FOR_ALL_TEMPLATES(var) FOR_ALL_TEMPLATES_FROM(var, 0)
+
+#define FOR_ALL_TEMPLATE_REPLACEMENTS_FROM(var, start) FOR_ALL_ITEMS_FROM(TemplateReplacement, template_replacement_index, var, start)
+#define FOR_ALL_TEMPLATE_REPLACEMENTS(var) FOR_ALL_TEMPLATE_REPLACEMENTS_FROM(var, 0)
+
+struct TemplateVehicle;
+struct TemplateReplacement;
+
+typedef uint16 TemplateID;
+
+
+static const uint16 CONSIST_HEAD = 0x0;
+static const uint16 CONSIST_TAIL = 0xffff;
+
+/** A pool allowing to store up to ~64k templates */
+typedef Pool TemplatePool;
+extern TemplatePool _template_pool;
+
+/// listing/sorting templates
+typedef GUIList GUITemplateList;
+
+struct TemplateVehicle : TemplatePool::PoolItem<&_template_pool>, BaseVehicle {
+private:
+ TemplateVehicle *next; ///< pointer to the next vehicle in the chain
+ TemplateVehicle *previous; ///< NOSAVE: pointer to the previous vehicle in the chain
+ TemplateVehicle *first; ///< NOSAVE: pointer to the first vehicle in the chain
+
+public:
+ friend const SaveLoad* GTD();
+ friend void AfterLoadTemplateVehicles();
+
+ // Template usage configuration
+ bool reuse_depot_vehicles;
+ bool keep_remaining_vehicles;
+ bool refit_as_template;
+
+ // Things derived from a virtual train
+ TemplateVehicle *other_multiheaded_part; ///< Multiheaded Engine support
+ Money value; ///< Value of the vehicle
+ Owner owner;
+ OwnerByte owner_b;
+
+ EngineID engine_type; ///< The type of engine used for this vehicle.
+ CargoID cargo_type; ///< type of cargo this vehicle is carrying
+ uint16 cargo_cap; ///< total capacity
+ byte cargo_subtype;
+
+ byte subtype;
+ RailTypeByte railtype;
+
+ VehicleID index;
+
+ uint16 real_consist_length;
+
+ uint16 max_speed;
+ uint32 power;
+ uint32 weight;
+ uint32 max_te;
+
+ byte spritenum;
+ SpriteID cur_image;
+ uint32 image_width;
+ const SpriteGroup *sgroup;
+
+ TemplateVehicle(VehicleType type = VEH_INVALID, EngineID e = INVALID_ENGINE, byte B = 0, Owner = _local_company);
+ TemplateVehicle(EngineID, RailVehicleInfo*);
+
+ TemplateVehicle(EngineID eid)
+ {
+ next = NULL;
+ previous = NULL;
+ first = this;
+ engine_type = eid;
+ this->reuse_depot_vehicles = true;
+ this->keep_remaining_vehicles = true;
+ this->refit_as_template = true;
+ }
+
+ ~TemplateVehicle();
+
+ inline TemplateVehicle* Next() const { return this->next; }
+ inline TemplateVehicle* Prev() const { return this->previous; }
+ inline TemplateVehicle* First() const { return this->first; }
+
+ void SetNext(TemplateVehicle*);
+ void SetPrev(TemplateVehicle*);
+ void SetFirst(TemplateVehicle*);
+
+ TemplateVehicle* GetNextUnit() const;
+ TemplateVehicle* GetPrevUnit();
+
+ bool IsSetReuseDepotVehicles() const { return this->reuse_depot_vehicles; }
+ bool IsSetKeepRemainingVehicles() const { return this->keep_remaining_vehicles; }
+ bool IsSetRefitAsTemplate() const { return this->refit_as_template; }
+ void ToggleReuseDepotVehicles() { this->reuse_depot_vehicles = !this->reuse_depot_vehicles; }
+ void ToggleKeepRemainingVehicles() { this->keep_remaining_vehicles = !this->keep_remaining_vehicles; }
+ void ToggleRefitAsTemplate() { this->refit_as_template = !this->refit_as_template; }
+
+ bool IsPrimaryVehicle() const { return this->IsFrontEngine(); }
+ inline bool IsFrontEngine() const { return HasBit(this->subtype, GVSF_FRONT); }
+ inline bool HasArticulatedPart() const { return this->Next() != NULL && this->Next()->IsArticulatedPart(); }
+
+ inline bool IsArticulatedPart() const { return HasBit(this->subtype, GVSF_ARTICULATED_PART); }
+ inline bool IsMultiheaded() const { return HasBit(this->subtype, GVSF_MULTIHEADED); }
+
+ inline bool IsFreeWagonChain() const { return HasBit(this->subtype, GVSF_FREE_WAGON); }
+
+ // since CmdBuildTemplateVehicle(...)
+ inline void SetFrontEngine() { SetBit(this->subtype, GVSF_FRONT); }
+ inline void SetEngine() { SetBit(this->subtype, GVSF_ENGINE); }
+ inline void SetArticulatedPart() { SetBit(this->subtype, GVSF_ARTICULATED_PART); }
+ inline void SetMultiheaded() { SetBit(this->subtype, GVSF_MULTIHEADED); }
+
+ inline void SetWagon() { SetBit(this->subtype, GVSF_WAGON); }
+ inline void SetFreeWagon() { SetBit(this->subtype, GVSF_FREE_WAGON); }
+
+ inline uint16 GetRealLength() const { return this->real_consist_length; }
+ inline void SetRealLength(uint16 len) { this->real_consist_length = len; }
+
+ int Length() const;
+
+ SpriteID GetImage(Direction) const;
+ SpriteID GetSpriteID() const;
+
+ short NumGroupsUsingTemplate() const;
+
+ short CountEnginesInChain();
+
+};
+
+void appendTemplateVehicle(TemplateVehicle*, TemplateVehicle*);
+void insertTemplateVehicle(TemplateVehicle*, TemplateVehicle*, TemplateVehicle*);
+
+void NeutralizeVehicleStatus(Train*);
+void SplitVehicleRemainders(Train*);
+
+// TemplateReplacement stuff
+
+typedef Pool TemplateReplacementPool;
+extern TemplateReplacementPool _template_replacement_pool;
+
+struct TemplateReplacement : TemplateReplacementPool::PoolItem<&_template_replacement_pool> {
+ GroupID group;
+ TemplateID sel_template;
+
+ TemplateReplacement(GroupID gid, TemplateID tid) { this->group=gid; this->sel_template=tid; }
+ TemplateReplacement() {}
+ ~TemplateReplacement() {}
+
+ inline GroupID Group() { return this->group; }
+ inline GroupID Template() { return this->sel_template; }
+
+ inline void SetGroup(GroupID gid) { this->group = gid; }
+ inline void SetTemplate(TemplateID tid) { this->sel_template = tid; }
+
+ inline TemplateID GetTemplateVehicleID() { return sel_template; }
+
+ inline const TemplateVehicle* GetTemplateVehicle()
+ {
+ const TemplateVehicle *tv;
+ FOR_ALL_TEMPLATES(tv) {
+ if (tv->index == this->sel_template) {
+ return tv;
+ }
+ }
+ return NULL;
+ }
+};
+
+TemplateReplacement* GetTemplateReplacementByGroupID(GroupID);
+TemplateReplacement* GetTemplateReplacementByTemplateID(TemplateID);
+bool IssueTemplateReplacement(GroupID, TemplateID);
+
+short deleteIllegalTemplateReplacements(GroupID);
+
+#endif /* TEMPLATE_VEH_H */
diff --git a/src/tbtr_template_vehicle_func.cpp b/src/tbtr_template_vehicle_func.cpp
new file mode 100644
index 0000000000..7064659296
--- /dev/null
+++ b/src/tbtr_template_vehicle_func.cpp
@@ -0,0 +1,547 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
+ */
+
+/** @file tbtr_template_vehicle_func.cpp Template-based train replacement: template vehicle functions. */
+
+#include "stdafx.h"
+#include "window_gui.h"
+#include "gfx_func.h"
+#include "window_func.h"
+#include "command_func.h"
+#include "vehicle_gui.h"
+#include "train.h"
+#include "strings_func.h"
+#include "vehicle_func.h"
+#include "core/geometry_type.hpp"
+#include "debug.h"
+
+#include "table/sprites.h"
+#include "table/strings.h"
+
+#include "cargoaction.h"
+#include "train.h"
+#include "company_func.h"
+#include "newgrf.h"
+#include "spritecache.h"
+#include "articulated_vehicles.h"
+#include "autoreplace_func.h"
+
+#include "depot_base.h"
+
+#include "tbtr_template_vehicle.h"
+#include "tbtr_template_vehicle_func.h"
+
+#include