summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bin/data/group.grfbin0 -> 5704 bytes
-rw-r--r--projects/openttd.vcproj9
-rw-r--r--projects/openttd_vs80.vcproj12
-rw-r--r--source.list3
-rw-r--r--src/aircraft_cmd.cpp4
-rw-r--r--src/autoreplace_cmd.cpp31
-rw-r--r--src/autoreplace_gui.cpp58
-rw-r--r--src/build_vehicle_gui.cpp10
-rw-r--r--src/command.cpp12
-rw-r--r--src/command.h6
-rw-r--r--src/economy.cpp2
-rw-r--r--src/engine.cpp30
-rw-r--r--src/engine.h7
-rw-r--r--src/gfxinit.cpp3
-rw-r--r--src/group.h97
-rw-r--r--src/group_cmd.cpp390
-rw-r--r--src/group_gui.cpp795
-rw-r--r--src/gui.h2
-rw-r--r--src/lang/english.txt41
-rw-r--r--src/misc.cpp2
-rw-r--r--src/openttd.cpp16
-rw-r--r--src/openttd.h2
-rw-r--r--src/player.h8
-rw-r--r--src/players.cpp10
-rw-r--r--src/saveload.cpp4
-rw-r--r--src/settings.cpp1
-rw-r--r--src/settings_gui.cpp1
-rw-r--r--src/strgen/strgen.cpp1
-rw-r--r--src/strings.cpp14
-rw-r--r--src/table/control_codes.h1
-rw-r--r--src/table/files.h1
-rw-r--r--src/table/sprites.h22
-rw-r--r--src/train_cmd.cpp40
-rw-r--r--src/variables.h1
-rw-r--r--src/vehicle.cpp32
-rw-r--r--src/vehicle.h2
-rw-r--r--src/vehicle_gui.cpp34
-rw-r--r--src/vehicle_gui.h18
-rw-r--r--src/window.h23
39 files changed, 1678 insertions, 67 deletions
diff --git a/bin/data/group.grf b/bin/data/group.grf
new file mode 100644
index 000000000..586a2bfd1
--- /dev/null
+++ b/bin/data/group.grf
Binary files differ
diff --git a/projects/openttd.vcproj b/projects/openttd.vcproj
index 0cbc3ce82..9bfe7b14e 100644
--- a/projects/openttd.vcproj
+++ b/projects/openttd.vcproj
@@ -453,6 +453,9 @@
RelativePath=".\..\src\gfxinit.h">
</File>
<File
+ RelativePath=".\..\src\group.h">
+ </File>
+ <File
RelativePath=".\..\src\gui.h">
</File>
<File
@@ -703,6 +706,9 @@
RelativePath=".\..\src\graph_gui.cpp">
</File>
<File
+ RelativePath=".\..\src\group_gui.cpp">
+ </File>
+ <File
RelativePath=".\..\src\industry_gui.cpp">
</File>
<File
@@ -791,6 +797,9 @@
RelativePath=".\..\src\dummy_land.cpp">
</File>
<File
+ RelativePath=".\..\src\group_cmd.cpp">
+ </File>
+ <File
RelativePath=".\..\src\industry_cmd.cpp">
</File>
<File
diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj
index c538253ed..87f6b0784 100644
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -832,6 +832,10 @@
>
</File>
<File
+ RelativePath=".\..\src\group.h"
+ >
+ </File>
+ <File
RelativePath=".\..\src\gui.h"
>
</File>
@@ -1164,6 +1168,10 @@
>
</File>
<File
+ RelativePath=".\..\src\group_gui.cpp"
+ >
+ </File>
+ <File
RelativePath=".\..\src\industry_gui.cpp"
>
</File>
@@ -1280,6 +1288,10 @@
>
</File>
<File
+ RelativePath=".\..\src\group_cmd.cpp"
+ >
+ </File>
+ <File
RelativePath=".\..\src\industry_cmd.cpp"
>
</File>
diff --git a/source.list b/source.list
index 5a18eddcf..740c43b99 100644
--- a/source.list
+++ b/source.list
@@ -118,6 +118,7 @@ functions.h
genworld.h
gfx.h
gfxinit.h
+group.h
gui.h
hal.h
heightmap.h
@@ -202,6 +203,7 @@ dock_gui.cpp
engine_gui.cpp
genworld_gui.cpp
graph_gui.cpp
+group_gui.cpp
industry_gui.cpp
intro_gui.cpp
main_gui.cpp
@@ -232,6 +234,7 @@ aircraft_cmd.cpp
clear_cmd.cpp
disaster_cmd.cpp
dummy_land.cpp
+group_cmd.cpp
industry_cmd.cpp
misc_cmd.cpp
order_cmd.cpp
diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp
index 0d4c3ba10..90192e023 100644
--- a/src/aircraft_cmd.cpp
+++ b/src/aircraft_cmd.cpp
@@ -1684,7 +1684,7 @@ static void AircraftEventHandler_HeliTakeOff(Vehicle *v, const AirportFTAClass *
/* check if the aircraft needs to be replaced or renewed and send it to a hangar if needed
* unless it is due for renewal but the engine is no longer available */
if (v->owner == _local_player && (
- EngineHasReplacementForPlayer(p, v->engine_type) ||
+ EngineHasReplacementForPlayer(p, v->engine_type, v->group_id) ||
((p->engine_renew && v->age - v->max_age > p->engine_renew_months * 30) &&
HASBIT(GetEngine(v->engine_type)->player_avail, _local_player))
)) {
@@ -1742,7 +1742,7 @@ static void AircraftEventHandler_Landing(Vehicle *v, const AirportFTAClass *apc)
if (v->current_order.type != OT_GOTO_DEPOT && v->owner == _local_player) {
/* only the vehicle owner needs to calculate the rest (locally) */
const Player* p = GetPlayer(v->owner);
- if (EngineHasReplacementForPlayer(p, v->engine_type) ||
+ if (EngineHasReplacementForPlayer(p, v->engine_type, v->group_id) ||
(p->engine_renew && v->age - v->max_age > (p->engine_renew_months * 30))) {
/* send the aircraft to the hangar at next airport */
_current_player = _local_player;
diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp
index 59e2ac44d..2774f2234 100644
--- a/src/autoreplace_cmd.cpp
+++ b/src/autoreplace_cmd.cpp
@@ -16,6 +16,7 @@
#include "train.h"
#include "aircraft.h"
#include "cargotype.h"
+#include "group.h"
/*
@@ -136,8 +137,17 @@ static int32 ReplaceVehicle(Vehicle **w, byte flags, int32 total_cost)
char vehicle_name[32];
CargoID replacement_cargo_type;
- new_engine_type = EngineReplacementForPlayer(p, old_v->engine_type);
- if (new_engine_type == INVALID_ENGINE) new_engine_type = old_v->engine_type;
+ /* If the vehicle belongs to a group, check if the group is protected from the global autoreplace.
+ * If not, chek if an global auto replacement is defined */
+ new_engine_type = (IsValidGroupID(old_v->group_id) && GetGroup(old_v->group_id)->replace_protection) ?
+ INVALID_ENGINE :
+ EngineReplacementForPlayer(p, old_v->engine_type, DEFAULT_GROUP);
+
+ /* If we don't set new_egnine_type previously, we try to check if an autoreplacement was defined
+ * for the group and the engine_type of the vehicle */
+ if (new_engine_type == INVALID_ENGINE && !IsDefaultGroupID(old_v->group_id)) {
+ new_engine_type = EngineReplacementForPlayer(p, old_v->engine_type, old_v->group_id);
+ }
replacement_cargo_type = GetNewCargoTypeForReplace(old_v, new_engine_type);
@@ -165,6 +175,7 @@ static int32 ReplaceVehicle(Vehicle **w, byte flags, int32 total_cost)
new_v = GetVehicle(_new_vehicle_id);
*w = new_v; //we changed the vehicle, so MaybeReplaceVehicle needs to work on the new one. Now we tell it what the new one is
+ new_v->group_id = old_v->group_id;
/* refit if needed */
if (replacement_cargo_type != CT_NO_REFIT) {
if (CmdFailed(DoCommand(0, new_v->index, replacement_cargo_type, DC_EXEC, GetCmdRefitVeh(new_v)))) {
@@ -194,6 +205,7 @@ static int32 ReplaceVehicle(Vehicle **w, byte flags, int32 total_cost)
new_v->profit_this_year = old_v->profit_this_year;
new_v->profit_last_year = old_v->profit_last_year;
new_v->service_interval = old_v->service_interval;
+ new_v->group_id = old_v->group_id;
new_front = true;
new_v->unitnumber = old_v->unitnumber; // use the same unit number
new_v->dest_tile = old_v->dest_tile;
@@ -211,6 +223,10 @@ static int32 ReplaceVehicle(Vehicle **w, byte flags, int32 total_cost)
if (temp_v != NULL) {
DoCommand(0, (new_v->index << 16) | temp_v->index, 1, DC_EXEC, CMD_MOVE_RAIL_VEHICLE);
}
+ } else if (!IsDefaultGroupID(old_v->group_id) && IsValidGroupID(old_v->group_id)) {
+ /* Increase the new num engines of the group for the ships, aircraft, and road vehicles
+ The old new num engine is decrease in the destroyvehicle function */
+ GetGroup(old_v->group_id)->num_engines[new_v->engine_type]++;
}
}
/* We are done setting up the new vehicle. Now we move the cargo from the old one to the new one */
@@ -305,8 +321,17 @@ int32 MaybeReplaceVehicle(Vehicle *v, bool check, bool display_costs)
if (!p->engine_renew ||
w->age - w->max_age < (p->engine_renew_months * 30) || // replace if engine is too old
w->max_age == 0) { // rail cars got a max age of 0
- if (!EngineHasReplacementForPlayer(p, w->engine_type)) // updates to a new model
+ /* If the vehicle belongs to a group, check if the group is protected from the global autoreplace.
+ If not, chek if an global auto remplacement is defined */
+ if (IsValidGroupID(w->group_id)) {
+ if (!EngineHasReplacementForPlayer(p, w->engine_type, w->group_id) && (
+ GetGroup(w->group_id)->replace_protection ||
+ !EngineHasReplacementForPlayer(p, w->engine_type, DEFAULT_GROUP))) {
+ continue;
+ }
+ } else if (!EngineHasReplacementForPlayer(p, w->engine_type, DEFAULT_GROUP)) {
continue;
+ }
}
/* Now replace the vehicle */
diff --git a/src/autoreplace_gui.cpp b/src/autoreplace_gui.cpp
index 03c1610e8..9a0e1467e 100644
--- a/src/autoreplace_gui.cpp
+++ b/src/autoreplace_gui.cpp
@@ -14,6 +14,7 @@
#include "variables.h"
#include "vehicle_gui.h"
#include "newgrf_engine.h"
+#include "group.h"
static RailType _railtype_selected_in_replace_gui;
@@ -150,8 +151,11 @@ static void GenerateReplaceVehList(Window *w, bool draw_left)
if (type == VEH_TRAIN && !GenerateReplaceRailList(e, draw_left, WP(w, replaceveh_d).wagon_btnstate)) continue; // special rules for trains
if (draw_left) {
+ const GroupID selected_group = WP(w, replaceveh_d).sel_group;
+ const uint num_engines = IsDefaultGroupID(selected_group) ? p->num_engines[e] : GetGroup(selected_group)->num_engines[e];
+
/* Skip drawing the engines we don't have any of and haven't set for replacement */
- if (p->num_engines[e] == 0 && EngineReplacementForPlayer(GetPlayer(_local_player), e) == INVALID_ENGINE) continue;
+ if (num_engines == 0 && EngineReplacementForPlayer(GetPlayer(_local_player), e, selected_group) == INVALID_ENGINE) continue;
} else {
/* This is for engines we can replace to and they should depend on what we selected to replace from */
if (!IsEngineBuildable(e, type, _local_player)) continue; // we need to be able to build the engine
@@ -202,7 +206,7 @@ static void GenerateLists(Window *w)
}
-void DrawEngineList(VehicleType type, int x, int y, const EngineList eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count);
+void DrawEngineList(VehicleType type, int x, int y, const EngineList eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group);
static void ReplaceVehicleWndProc(Window *w, WindowEvent *e)
{
@@ -231,6 +235,7 @@ static void ReplaceVehicleWndProc(Window *w, WindowEvent *e)
Player *p = GetPlayer(_local_player);
EngineID selected_id[2];
+ const GroupID selected_group = WP(w,replaceveh_d).sel_group;
selected_id[0] = WP(w, replaceveh_d).sel_engine[0];
selected_id[1] = WP(w, replaceveh_d).sel_engine[1];
@@ -242,15 +247,15 @@ static void ReplaceVehicleWndProc(Window *w, WindowEvent *e)
SetWindowWidgetDisabledState(w, 4,
selected_id[0] == INVALID_ENGINE ||
selected_id[1] == INVALID_ENGINE ||
- EngineReplacementForPlayer(p, selected_id[1]) != INVALID_ENGINE ||
- EngineReplacementForPlayer(p, selected_id[0]) == selected_id[1]);
+ EngineReplacementForPlayer(p, selected_id[1], selected_group) != INVALID_ENGINE ||
+ EngineReplacementForPlayer(p, selected_id[0], selected_group) == selected_id[1]);
/* Disable the "Stop Replacing" button if:
* The left list (existing vehicle) is empty
* or The selected vehicle has no replacement set up */
SetWindowWidgetDisabledState(w, 6,
selected_id[0] == INVALID_ENGINE ||
- !EngineHasReplacementForPlayer(p, selected_id[0]));
+ !EngineHasReplacementForPlayer(p, selected_id[0], selected_group));
/* now the actual drawing of the window itself takes place */
SetDParam(0, _vehicle_type_names[w->window_number]);
@@ -277,10 +282,10 @@ static void ReplaceVehicleWndProc(Window *w, WindowEvent *e)
/* sets up the string for the vehicle that is being replaced to */
if (selected_id[0] != INVALID_ENGINE) {
- if (!EngineHasReplacementForPlayer(p, selected_id[0])) {
+ if (!EngineHasReplacementForPlayer(p, selected_id[0], selected_group)) {
SetDParam(0, STR_NOT_REPLACING);
} else {
- SetDParam(0, GetCustomEngineName(EngineReplacementForPlayer(p, selected_id[0])));
+ SetDParam(0, GetCustomEngineName(EngineReplacementForPlayer(p, selected_id[0], selected_group)));
}
} else {
SetDParam(0, STR_NOT_REPLACING_VEHICLE_SELECTED);
@@ -296,7 +301,7 @@ static void ReplaceVehicleWndProc(Window *w, WindowEvent *e)
EngineID end = min((i == 0 ? w->vscroll.cap : w->vscroll2.cap) + start, EngList_Count(&list));
/* Do the actual drawing */
- DrawEngineList((VehicleType)w->window_number, x, 15, list, start, end, WP(w, replaceveh_d).sel_engine[i], i == 0);
+ DrawEngineList((VehicleType)w->window_number, x, 15, list, start, end, WP(w, replaceveh_d).sel_engine[i], i == 0, selected_group);
/* Also draw the details if an engine is selected */
if (WP(w, replaceveh_d).sel_engine[i] != INVALID_ENGINE) {
@@ -328,12 +333,12 @@ static void ReplaceVehicleWndProc(Window *w, WindowEvent *e)
case 4: { /* Start replacing */
EngineID veh_from = WP(w, replaceveh_d).sel_engine[0];
EngineID veh_to = WP(w, replaceveh_d).sel_engine[1];
- DoCommandP(0, 3, veh_from + (veh_to << 16), NULL, CMD_SET_AUTOREPLACE);
+ DoCommandP(0, 3 + (WP(w, replaceveh_d).sel_group << 16) , veh_from + (veh_to << 16), NULL, CMD_SET_AUTOREPLACE);
} break;
case 6: { /* Stop replacing */
EngineID veh_from = WP(w, replaceveh_d).sel_engine[0];
- DoCommandP(0, 3, veh_from + (INVALID_ENGINE << 16), NULL, CMD_SET_AUTOREPLACE);
+ DoCommandP(0, 3 + (WP(w, replaceveh_d).sel_group << 16), veh_from + (INVALID_ENGINE << 16), NULL, CMD_SET_AUTOREPLACE);
} break;
case 7:
@@ -509,4 +514,37 @@ void ShowReplaceVehicleWindow(VehicleType vehicletype)
w->caption_color = _local_player;
w->vscroll2.cap = w->vscroll.cap; // these two are always the same
+ WP(w, replaceveh_d).sel_group = DEFAULT_GROUP;
+ }
+
+void ShowReplaceGroupVehicleWindow(GroupID id_g, VehicleType vehicletype)
+{
+ Window *w;
+
+ DeleteWindowById(WC_REPLACE_VEHICLE, vehicletype);
+
+ switch (vehicletype) {
+ default: NOT_REACHED();
+ case VEH_TRAIN:
+ w = AllocateWindowDescFront(&_replace_rail_vehicle_desc, vehicletype);
+ w->vscroll.cap = 8;
+ w->resize.step_height = 14;
+ WP(w, replaceveh_d).wagon_btnstate = true;
+ break;
+ case VEH_ROAD:
+ w = AllocateWindowDescFront(&_replace_road_vehicle_desc, vehicletype);
+ w->vscroll.cap = 8;
+ w->resize.step_height = 14;
+ break;
+ case VEH_SHIP:
+ case VEH_AIRCRAFT:
+ w = AllocateWindowDescFront(&_replace_ship_aircraft_vehicle_desc, vehicletype);
+ w->vscroll.cap = 4;
+ w->resize.step_height = 24;
+ break;
+ }
+
+ w->caption_color = _local_player;
+ WP(w, replaceveh_d).sel_group = id_g;
+ w->vscroll2.cap = w->vscroll.cap; // these two are always the same
}
diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp
index 33a08f40c..746d8bbc9 100644
--- a/src/build_vehicle_gui.cpp
+++ b/src/build_vehicle_gui.cpp
@@ -27,6 +27,7 @@
#include "date.h"
#include "strings.h"
#include "cargotype.h"
+#include "group.h"
enum BuildVehicleWidgets {
@@ -759,7 +760,7 @@ static void DrawVehicleEngine(byte type, int x, int y, EngineID engine, SpriteID
* @param selected_id what engine to highlight as selected, if any
* @param show_count Display the number of vehicles (used by autoreplace)
*/
-void DrawEngineList(VehicleType type, int x, int y, const EngineList eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count)
+void DrawEngineList(VehicleType type, int x, int y, const EngineList eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group)
{
byte step_size = GetVehicleListHeight(type);
byte x_offset = 0;
@@ -795,11 +796,12 @@ void DrawEngineList(VehicleType type, int x, int y, const EngineList eng_list, u
for (; min < max; min++, y += step_size) {
const EngineID engine = eng_list[min];
+ const uint num_engines = IsDefaultGroupID(selected_group) ? p->num_engines[engine] : GetGroup(selected_group)->num_engines[engine];
DrawString(x + x_offset, y, GetCustomEngineName(engine), engine == selected_id ? 0xC : 0x10);
- DrawVehicleEngine(type, x, y + y_offset, engine, (show_count && p->num_engines[engine] == 0) ? PALETTE_CRASH : GetEnginePalette(engine, _local_player));
+ DrawVehicleEngine(type, x, y + y_offset, engine, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(engine, _local_player));
if (show_count) {
- SetDParam(0, p->num_engines[engine]);
+ SetDParam(0, num_engines);
DrawStringRightAligned(213, y + (GetVehicleListHeight(type) == 14 ? 3 : 8), STR_TINY_BLACK, 0);
}
}
@@ -833,7 +835,7 @@ static void DrawBuildVehicleWindow(Window *w)
SetDParam(0, bv->filter.railtype + STR_881C_NEW_RAIL_VEHICLES); // This should only affect rail vehicles
DrawWindowWidgets(w);
- DrawEngineList(bv->vehicle_type, 2, 27, bv->eng_list, w->vscroll.pos, max, bv->sel_engine, false);
+ DrawEngineList(bv->vehicle_type, 2, 27, bv->eng_list, w->vscroll.pos, max, bv->sel_engine, false, DEFAULT_GROUP);
if (bv->sel_engine != INVALID_ENGINE) {
const Widget *wi = &w->widget[BUILD_VEHICLE_WIDGET_PANEL];
diff --git a/src/command.cpp b/src/command.cpp
index 49029ee7e..3fa2bcfec 100644
--- a/src/command.cpp
+++ b/src/command.cpp
@@ -168,6 +168,12 @@ DEF_COMMAND(CmdMassStartStopVehicle);
DEF_COMMAND(CmdDepotSellAllVehicles);
DEF_COMMAND(CmdDepotMassAutoReplace);
+DEF_COMMAND(CmdCreateGroup);
+DEF_COMMAND(CmdRenameGroup);
+DEF_COMMAND(CmdDeleteGroup);
+DEF_COMMAND(CmdAddVehicleGroup);
+DEF_COMMAND(CmdAddSharedVehicleGroup);
+DEF_COMMAND(CmdRemoveAllVehiclesGroup);
/* The master command table */
static const Command _command_proc_table[] = {
{CmdBuildRailroadTrack, 0}, /* 0 */
@@ -313,6 +319,12 @@ static const Command _command_proc_table[] = {
{CmdMassStartStopVehicle, 0}, /* 117 */
{CmdDepotSellAllVehicles, 0}, /* 118 */
{CmdDepotMassAutoReplace, 0}, /* 119 */
+ {CmdCreateGroup, 0}, /* 120 */
+ {CmdDeleteGroup, 0}, /* 121 */
+ {CmdRenameGroup, 0}, /* 122 */
+ {CmdAddVehicleGroup, 0}, /* 123 */
+ {CmdAddSharedVehicleGroup, 0}, /* 124 */
+ {CmdRemoveAllVehiclesGroup, 0}, /* 125 */
};
/* This function range-checks a cmd, and checks if the cmd is not NULL */
diff --git a/src/command.h b/src/command.h
index 7a6119652..4cf9844e3 100644
--- a/src/command.h
+++ b/src/command.h
@@ -143,6 +143,12 @@ enum {
CMD_MASS_START_STOP = 117,
CMD_DEPOT_SELL_ALL_VEHICLES = 118,
CMD_DEPOT_MASS_AUTOREPLACE = 119,
+ CMD_CREATE_GROUP = 120,
+ CMD_DELETE_GROUP = 121,
+ CMD_RENAME_GROUP = 122,
+ CMD_ADD_VEHICLE_GROUP = 123,
+ CMD_ADD_SHARED_VEHICLE_GROUP = 124,
+ CMD_REMOVE_ALL_VEHICLES_GROUP = 125,
};
enum {
diff --git a/src/economy.cpp b/src/economy.cpp
index d6f0e629c..989500147 100644
--- a/src/economy.cpp
+++ b/src/economy.cpp
@@ -38,6 +38,7 @@
#include "date.h"
#include "cargotype.h"
#include "player_face.h"
+#include "group.h"
/* Score info */
const ScoreInfo _score_info[] = {
@@ -359,6 +360,7 @@ void ChangeOwnershipOfPlayerItems(PlayerID old_player, PlayerID new_player)
DeleteVehicle(v);
} else {
v->owner = new_player;
+ v->group_id = DEFAULT_GROUP;
if (IsEngineCountable(v)) GetPlayer(new_player)->num_engines[v->engine_type]++;
switch (v->type) {
case VEH_TRAIN: if (IsFrontEngine(v)) v->unitnumber = ++num_train; break;
diff --git a/src/engine.cpp b/src/engine.cpp
index e4d1e6e8d..b0336cc2d 100644
--- a/src/engine.cpp
+++ b/src/engine.cpp
@@ -20,6 +20,7 @@
#include "newgrf_cargo.h"
#include "date.h"
#include "table/engines.h"
+#include "group.h"
EngineInfo _engine_info[TOTAL_NUM_ENGINES];
RailVehicleInfo _rail_vehicle_info[NUM_TRAIN_ENGINES];
@@ -481,6 +482,7 @@ static EngineRenew *AllocateEngineRenew()
er->to = INVALID_ENGINE;
er->next = NULL;
+ er->group_id = DEFAULT_GROUP;
return er;
}
@@ -493,12 +495,12 @@ static EngineRenew *AllocateEngineRenew()
/**
* Retrieves the EngineRenew that specifies the replacement of the given
* engine type from the given renewlist */
-static EngineRenew *GetEngineReplacement(EngineRenewList erl, EngineID engine)
+static EngineRenew *GetEngineReplacement(EngineRenewList erl, EngineID engine, GroupID group)
{
EngineRenew *er = (EngineRenew *)erl;
while (er) {
- if (er->from == engine) return er;
+ if (er->from == engine && er->group_id == group) return er;
er = er->next;
}
return NULL;
@@ -517,18 +519,18 @@ void RemoveAllEngineReplacement(EngineRenewList *erl)
*erl = NULL; // Empty list
}
-EngineID EngineReplacement(EngineRenewList erl, EngineID engine)
+EngineID EngineReplacement(EngineRenewList erl, EngineID engine, GroupID group)
{
- const EngineRenew *er = GetEngineReplacement(erl, engine);
+ const EngineRenew *er = GetEngineReplacement(erl, engine, group);
return er == NULL ? INVALID_ENGINE : er->to;
}
-int32 AddEngineReplacement(EngineRenewList *erl, EngineID old_engine, EngineID new_engine, uint32 flags)
+int32 AddEngineReplacement(EngineRenewList *erl, EngineID old_engine, EngineID new_engine, GroupID group, uint32 flags)
{
EngineRenew *er;
/* Check if the old vehicle is already in the list */
- er = GetEngineReplacement(*erl, old_engine);
+ er = GetEngineReplacement(*erl, old_engine, group);
if (er != NULL) {
if (flags & DC_EXEC) er->to = new_engine;
return 0;
@@ -540,6 +542,7 @@ int32 AddEngineReplacement(EngineRenewList *erl, EngineID old_engine, EngineID n
if (flags & DC_EXEC) {
er->from = old_engine;
er->to = new_engine;
+ er->group_id = group;
/* Insert before the first element */
er->next = (EngineRenew *)(*erl);
@@ -549,14 +552,14 @@ int32 AddEngineReplacement(EngineRenewList *erl, EngineID old_engine, EngineID n
return 0;
}
-int32 RemoveEngineReplacement(EngineRenewList *erl, EngineID engine, uint32 flags)
+int32 RemoveEngineReplacement(EngineRenewList *erl, EngineID engine, GroupID group, uint32 flags)
{
EngineRenew *er = (EngineRenew *)(*erl);
EngineRenew *prev = NULL;
while (er)
{
- if (er->from == engine) {
+ if (er->from == engine && er->group_id == group) {
if (flags & DC_EXEC) {
if (prev == NULL) { // First element
/* The second becomes the new first element */
@@ -577,11 +580,11 @@ int32 RemoveEngineReplacement(EngineRenewList *erl, EngineID engine, uint32 flag
}
static const SaveLoad _engine_renew_desc[] = {
- SLE_VAR(EngineRenew, from, SLE_UINT16),
- SLE_VAR(EngineRenew, to, SLE_UINT16),
-
- SLE_REF(EngineRenew, next, REF_ENGINE_RENEWS),
+ SLE_VAR(EngineRenew, from, SLE_UINT16),
+ SLE_VAR(EngineRenew, to, SLE_UINT16),
+ SLE_REF(EngineRenew, next, REF_ENGINE_RENEWS),
+ SLE_CONDVAR(EngineRenew, group_id, SLE_UINT16, 60, SL_MAX_VERSION),
SLE_END()
};
@@ -607,6 +610,9 @@ static void Load_ERNW()
er = GetEngineRenew(index);
SlObject(er, _engine_renew_desc);
+
+ /* Advanced vehicle lists got added */
+ if (CheckSavegameVersion(60)) er->group_id = DEFAULT_GROUP;
}
}
diff --git a/src/engine.h b/src/engine.h
index c54e3bb10..b34452c07 100644
--- a/src/engine.h
+++ b/src/engine.h
@@ -272,6 +272,7 @@ struct EngineRenew {
EngineID from;
EngineID to;
EngineRenew *next;
+ GroupID group_id;
};
/**
@@ -317,7 +318,7 @@ void RemoveAllEngineReplacement(EngineRenewList* erl);
* @return The engine type to replace with, or INVALID_ENGINE if no
* replacement is in the list.
*/
-EngineID EngineReplacement(EngineRenewList erl, EngineID engine);
+EngineID EngineReplacement(EngineRenewList erl, EngineID engine, GroupID group);
/**
* Add an engine replacement to the given renewlist.
@@ -327,7 +328,7 @@ EngineID EngineReplacement(EngineRenewList erl, EngineID engine);
* @param flags The calling command flags.
* @return 0 on success, CMD_ERROR on failure.
*/
-int32 AddEngineReplacement(EngineRenewList* erl, EngineID old_engine, EngineID new_engine, uint32 flags);
+int32 AddEngineReplacement(EngineRenewList* erl, EngineID old_engine, EngineID new_engine, GroupID group, uint32 flags);
/**
* Remove an engine replacement from a given renewlist.
@@ -336,7 +337,7 @@ int32 AddEngineReplacement(EngineRenewList* erl, EngineID old_engine, EngineID n
* @param flags The calling command flags.
* @return 0 on success, CMD_ERROR on failure.
*/
-int32 RemoveEngineReplacement(EngineRenewList* erl, EngineID engine, uint32 flags);
+int32 RemoveEngineReplacement(EngineRenewList* erl, EngineID engine, GroupID group, uint32 flags);
/** When an engine is made buildable or is removed from being buildable, add/remove it from the build/autoreplace lists
* @param type The type of engine
diff --git a/src/gfxinit.cpp b/src/gfxinit.cpp
index 01c1ded80..6ff742f0c 100644
--- a/src/gfxinit.cpp
+++ b/src/gfxinit.cpp
@@ -393,6 +393,9 @@ static void LoadSpriteTables()
assert(load_index == SPR_ROADSTOP_BASE);
load_index += LoadGrfFile("roadstops.grf", load_index, i++);
+ assert(load_index == SPR_GROUP_BASE);
+ load_index += LoadGrfFile("group.grf", load_index, i++);
+
/* Initialize the unicode to sprite mapping table */
InitializeUnicodeGlyphMap();
diff --git a/src/group.h b/src/group.h
new file mode 100644
index 000000000..f68306293
--- /dev/null
+++ b/src/group.h
@@ -0,0 +1,97 @@
+/* $Id$ */
+
+/** @file group.h */
+
+#ifndef GROUP_H
+#define GROUP_H
+
+#include "oldpool.h"
+
+enum {
+ DEFAULT_GROUP = 0xFFFE,
+ INVALID_GROUP = 0xFFFF,
+};
+
+struct Group {
+ StringID string_id; ///< Group Name
+
+ uint16 num_vehicle; ///< Number of vehicles wich belong to the group
+ PlayerID owner; ///< Group Owner
+ GroupID index; ///< Array index
+ VehicleTypeByte vehicle_type; ///< Vehicle type of the group
+
+ bool replace_protection; ///< If set to true, the global autoreplace have no effect on the group
+ uint16 num_engines[TOTAL_NUM_ENGINES]; ///< Caches the number of engines of each type the player owns (no need to save this)
+};
+
+DECLARE_OLD_POOL(Group, Group, 5, 2047)
+
+
+static inline bool IsValidGroup(const Group *g)
+{
+ return g->string_id != STR_NULL;
+}
+
+static inline void DestroyGroup(Group *g)
+{
+ DeleteName(g->string_id);
+}
+
+static inline void DeleteGroup(Group *g)
+{
+ DestroyGroup(g);
+ g->string_id = STR_NULL;
+}
+
+static inline bool IsValidGroupID(GroupID index)
+{
+ return index < GetGroupPoolSize() && IsValidGroup(GetGroup(index));
+}
+
+static inline bool IsDefaultGroupID(GroupID index)
+{
+ return (index == DEFAULT_GROUP);
+}
+
+static inline StringID GetGroupName(GroupID index)
+{
+ if (!IsValidGroupID(index)) return STR_NULL;
+
+ return GetGroup(index)->string_id;
+}
+
+
+#define FOR_ALL_GROUPS_FROM(g, start) for (g = GetGroup(start); g != NULL; g = (g->index + 1U < GetGroupPoolSize()) ? GetGroup(g->index + 1) : NULL) if (IsValidGroup(g))
+#define FOR_ALL_GROUPS(g) FOR_ALL_GROUPS_FROM(g, 0)
+
+/**
+ * Get the current size of the GroupPool
+ */
+static inline uint GetGroupArraySize(void)
+{
+ const Group *g;
+ uint num = 0;
+
+ FOR_ALL_GROUPS(g) num++;
+
+ return num;
+}
+
+static inline void IncreaseGroupNumVehicle(GroupID id_g)
+{
+ if (IsValidGroupID(id_g)) GetGroup(id_g)->num_vehicle++;
+}
+
+static inline void DecreaseGroupNumVehicle(GroupID id_g)
+{
+ if (IsValidGroupID(id_g)) GetGroup(id_g)->num_vehicle--;
+}
+
+
+void InitializeGroup();
+void SetTrainGroupID(Vehicle *v, GroupID grp);
+void UpdateTrainGroupID(Vehicle *v);
+void RemoveVehicleFromGroup(const Vehicle *v);
+void RemoveAllGroupsForPlayer(const Player *p);
+
+#endif /* GROUP_H */
diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp
new file mode 100644
index 000000000..b44e92fbe
--- /dev/null
+++ b/src/group_cmd.cpp
@@ -0,0 +1,390 @@
+/* $Id$ */
+
+/** @file group_cmd.cpp Handling of the engine groups */
+
+#include "stdafx.h"
+#include "openttd.h"
+#include "functions.h"
+#include "player.h"
+#include "table/strings.h"
+#include "command.h"
+#include "vehicle.h"
+#include "saveload.h"
+#include "debug.h"
+#include "group.h"
+#include "train.h"
+#include "aircraft.h"
+#include "string.h"
+
+/**
+ * Update the num engines of a groupID. Decrease the old one and increase the new one
+ * @note called in SetTrainGroupID and UpdateTrainGroupID
+ * @param i EngineID we have to update
+ * @param old_g index of the old group
+ * @param new_g index of the new group
+ */
+static inline void UpdateNumEngineGroup(EngineID i, GroupID old_g, GroupID new_g)
+{
+ if (old_g != new_g) {
+ /* Decrease the num engines of EngineID i of the old group if it's not the default one */
+ if (!IsDefaultGroupID(old_g) && IsValidGroupID(old_g)) GetGroup(old_g)->num_engines[i]--;
+
+ /* Increase the num engines of EngineID i of the new group if it's not the new one */
+ if (!IsDefaultGroupID(new_g) && IsValidGroupID(new_g)) GetGroup(new_g)->num_engines[i]++;
+ }
+}
+
+
+/**
+ * Called if a new block is added to the group-pool
+ */
+static void GroupPoolNewBlock(uint start_item)
+{
+ /* We don't use FOR_ALL here, because FOR_ALL skips invalid items.
+ * TODO - This is just a temporary stage, this will be removed. */
+ for (Group *g = GetGroup(start_item); g != NULL; g = (g->index + 1U < GetGroupPoolSize()) ? GetGroup(g->index + 1) : NULL) g->index = start_item++;
+}
+
+DEFINE_OLD_POOL(Group, Group, GroupPoolNewBlock, NULL)
+
+static Group *AllocateGroup(void)
+{
+ /* We don't use FOR_ALL here, because FOR_ALL skips invalid items.
+ * TODO - This is just a temporary stage, this will be removed. */
+ for (Group *g = GetGroup(0); g != NULL; g = (g->index + 1U < GetGroupPoolSize()) ? GetGroup(g->index + 1) : NULL) {
+ if (!IsValidGroup(g)) {
+ const GroupID index = g->index;
+
+ memset(g, 0, sizeof(*g));
+ g->index = index;
+
+ return g;
+ }
+ }
+
+ /* Check if we can add a block to the pool */
+ return (AddBlockToPool(&_Group_pool)) ? AllocateGroup() : NULL;
+}
+
+void InitializeGroup(void)
+{
+ CleanPool(&_Group_pool);
+ AddBlockToPool(&_Group_pool);
+}
+
+
+/**
+ * Add a vehicle to a group
+ * @param tile unused
+ * @param p1 vehicle type
+ * @param p2 unused
+ */
+int32 CmdCreateGroup(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
+{
+ VehicleType vt = (VehicleType)p1;
+ if (!IsPlayerBuildableVehicleType(vt)) return CMD_ERROR;
+
+ Group *g = AllocateGroup();
+ if (g == NULL) return CMD_ERROR;
+
+ if (flags & DC_EXEC) {
+ g->owner = _current_player;
+ g->string_id = STR_SV_GROUP_NAME;
+ g->replace_protection = false;
+ g->vehicle_type = vt;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Add a vehicle to a group
+ * @param tile unused
+ * @param p1 index of array group
+ * - p1 bit 0-15 : GroupID
+ * @param p2 unused
+ */
+int32 CmdDeleteGroup(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
+{
+ if (!IsValidGroupID(p1)) return CMD_ERROR;
+
+ Group *g = GetGroup(p1);
+ if (g->owner != _current_player) return CMD_ERROR;
+
+ if (flags & DC_EXEC) {
+ Vehicle *v;
+
+ /* Add all vehicles belong to the group to the default group */
+ FOR_ALL_VEHICLES(v) {
+ if (v->group_id == g->index && v->type == g->vehicle_type) v->group_id = DEFAULT_GROUP;
+ }
+
+ /* If we set an autoreplace for the group we delete, remove it. */
+ if (_current_player < MAX_PLAYERS) {
+ Player *p;
+ EngineRenew *er;
+
+ p = GetPlayer(_current_player);
+ FOR_ALL_ENGINE_RENEWS(er) {
+ if (er->group_id == g->index) RemoveEngineReplacementForPlayer(p, er->from, g->index, flags);
+ }
+ }
+
+ /* Delete the Replace Vehicle Windows */
+ DeleteWindowById(WC_REPLACE_VEHICLE, g->vehicle_type);
+ DeleteGroup(g);
+ }
+
+ return 0;
+}
+
+
+/**
+ * Rename a group
+ * @param tile unused
+ * @param p1 index of array group
+ * - p1 bit 0-15 : GroupID
+ * @param p2 unused
+ */
+int32 CmdRenameGroup(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
+{
+ if (!IsValidGroupID(p1) || StrEmpty(_cmd_text)) return CMD_ERROR;
+
+ /* Create the name */
+ StringID str = AllocateName(_cmd_text, 0);
+ if (str == STR_NULL) return CMD_ERROR;
+
+ if (flags & DC_EXEC) {
+ Group *g = GetGroup(p1);
+
+ /* Delete the old name */
+ DeleteName(g->string_id);
+ /* Assign the new one */
+ g->string_id = str;
+ g->owner = _current_player;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Add a vehicle to a group
+ * @param tile unused
+ * @param p1 index of array group
+ * - p1 bit 0-15 : GroupID
+ * @param p2 vehicle to add to a group
+ * - p2 bit 0-15 : VehicleID
+ */
+int32 CmdAddVehicleGroup(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
+{
+ GroupID new_g = p1;
+
+ if (!IsValidVehicleID(p2) || !IsValidGroupID(new_g)) return CMD_ERROR;
+
+ Vehicle *v = GetVehicle(p2);
+ if (v->owner != _current_player || (v->type == VEH_TRAIN && !IsFrontEngine(v))) return CMD_ERROR;
+
+ if (flags & DC_EXEC) {
+ DecreaseGroupNumVehicle(v->group_id);
+ IncreaseGroupNumVehicle(new_g);
+
+ switch (v->type) {
+ default: NOT_REACHED();
+ case VEH_TRAIN:
+ SetTrainGroupID(v, new_g);
+ break;
+ case VEH_ROAD:
+ case VEH_SHIP:
+ case VEH_AIRCRAFT:
+ if (IsEngineCountable(v)) UpdateNumEngineGroup(v->engine_type, v->group_id, new_g);
+ v->group_id = new_g;
+ break;
+ }
+
+ /* Update the Replace Vehicle Windows */
+ InvalidateWindow(WC_REPLACE_VEHICLE, v->type);
+ }
+
+ return 0;
+}
+
+/**
+ * Add all shared vehicles of all vehicles from a group
+ * @param tile unused
+ * @param p1 index of group array
+ * - p1 bit 0-15 : GroupID
+ * @param p2 type of vehicles
+ */
+int32 CmdAddSharedVehicleGroup(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
+{
+ VehicleType type = (VehicleType)p2;
+ if (!IsValidGroupID(p1) || !IsPlayerBuildableVehicleType(type)) return CMD_ERROR;
+
+ if (flags & DC_EXEC) {
+ Vehicle *v;
+ VehicleType type = (VehicleType)p2;
+ GroupID id_g = p1;
+ uint subtype = (type == VEH_AIRCRAFT) ? AIR_AIRCRAFT : 0;
+
+ /* Find the first front engine which belong to the group id_g
+ * then add all shared vehicles of this front engine to the group id_g */
+ FOR_ALL_VEHICLES(v) {
+ if ((v->type == type) && (
+ (type == VEH_TRAIN && IsFrontEngine(v)) ||
+ (type != VEH_TRAIN && v->subtype <= subtype))) {
+ if (v->group_id != id_g) continue;
+
+ /* For each shared vehicles add it to the group */
+ for (Vehicle *v2 = GetFirstVehicleFromSharedList(v); v2 != NULL; v2 = v2->next_shared) {
+ if (v2->group_id != id_g) CmdAddVehicleGroup(tile, flags, id_g, v2->index);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * Remove all vehicles from a group
+ * @param tile unused
+ * @param p1 index of group array
+ * - p1 bit 0-15 : GroupID
+ * @param p2 type of vehicles
+ */
+int32 CmdRemoveAllVehiclesGroup(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
+{
+ VehicleType type = (VehicleType)p2;
+ if (!IsValidGroupID(p1) || !IsPlayerBuildableVehicleType(type)) return CMD_ERROR;
+
+ if (flags & DC_EXEC) {
+ GroupID old_g = p1;
+ uint subtype = (type == VEH_AIRCRAFT) ? AIR_AIRCRAFT : 0;
+ Vehicle *v;
+
+ /* Find each Vehicle that belongs to the group old_g and add it to the default group */
+ FOR_ALL_VEHICLES(v) {
+ if ((v->type == type) && (
+ (type == VEH_TRAIN && IsFrontEngine(v)) ||
+ (type != VEH_TRAIN && v->subtype <= subtype))) {
+ if (v->group_id != old_g) continue;
+
+ /* Add The Vehicle to the default group */
+ CmdAddVehicleGroup(tile, flags, DEFAULT_GROUP, v->index);
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Decrease the num_vehicle variable before delete an front engine from a group
+ * @note Called in CmdSellRailWagon and DeleteLasWagon,
+ * @param v FrontEngine of the train we want to remove.
+ */
+void RemoveVehicleFromGroup(const Vehicle *v)
+{
+ if (!IsValidVehicle(v) || v->type != VEH_TRAIN || !IsFrontEngine(v)) return;
+
+ if (!IsDefaultGroupID(v->group_id)) DecreaseGroupNumVehicle(v->group_id);
+}
+
+
+/**
+ * Affect the groupID of a train to new_g.
+ * @note called in CmdAddVehicleGroup and CmdMoveRailVehicle
+ * @param v First vehicle of the chain.
+ * @param new_g index of array group
+ */
+void SetTrainGroupID(Vehicle *v, GroupID new_g)
+{
+ if (!IsValidGroupID(new_g) && !IsDefaultGroupID(new_g)) return;
+
+ assert(IsValidVehicle(v) && v->type == VEH_TRAIN && IsFrontEngine(v));
+
+ for (Vehicle *u = v; u != NULL; u = u->next) {
+ if (IsEngineCountable(u)) UpdateNumEngineGroup(u->engine_type, u->group_id, new_g);
+
+ u->group_id = new_g;
+ }
+
+ /* Update the Replace Vehicle Windows */
+ InvalidateWindow(WC_REPLACE_VEHICLE, VEH_TRAIN);
+}
+
+
+/**
+ * Recalculates the groupID of a train. Should be called each time a vehicle is added
+ * to/removed from the chain,.
+ * @note this needs to be called too for 'wagon chains' (in the depot, without an engine)
+ * @note Called in CmdBuildRailVehicle, CmdBuildRailWagon, CmdMoveRailVehicle, CmdSellRailWagon
+ * @param v First vehicle of the chain.
+ */
+void UpdateTrainGroupID(Vehicle *v)
+{
+ assert(IsValidVehicle(v) && v->type == VEH_TRAIN && (IsFrontEngine(v) || IsFreeWagon(v)));
+
+ GroupID new_g = IsFrontEngine(v) ? v->group_id : (GroupID)DEFAULT_GROUP;
+ for (Vehicle *u = v; u != NULL; u = u->next) {
+ if (IsEngineCountable(u)) UpdateNumEngineGroup(u->engine_type, u->group_id, new_g);
+
+ u->group_id = new_g;
+ }
+
+ /* Update the Replace Vehicle Windows */
+ InvalidateWindow(WC_REPLACE_VEHICLE, VEH_TRAIN);
+}
+
+
+void RemoveAllGroupsForPlayer(const Player *p)
+{
+ Group *g;
+
+ FOR_ALL_GROUPS(g) {
+ if (p->index == g->owner) DeleteGroup(g);
+ }
+}
+
+
+static const SaveLoad _group_desc[] = {
+ SLE_VAR(Group, string_id, SLE_UINT16),
+ SLE_VAR(Group, num_vehicle, SLE_UINT16),
+ SLE_VAR(Group, owner, SLE_UINT8),
+ SLE_VAR(Group, vehicle_type, SLE_UINT8),
+ SLE_VAR(Group, replace_protection, SLE_BOOL),
+ SLE_END()
+};
+
+
+static void Save_GROUP(void)
+{
+ Group *g;
+
+ FOR_ALL_GROUPS(g) {
+ SlSetArrayIndex(g->index);
+ SlObject(g, _group_desc);
+ }
+}
+
+
+static void Load_GROUP(void)
+{
+ int index;
+
+ while ((index = SlIterateArray()) != -1) {
+ if (!AddBlockIfNeeded(&_Group_pool, index)) {
+ error("Groups: failed loading savegame: too many groups");
+ }
+
+ Group *g = GetGroup(index);
+ SlObject(g, _group_desc);
+ }
+}
+
+extern const ChunkHandler _group_chunk_handlers[] = {
+ { 'GRPS', Save_GROUP, Load_GROUP, CH_ARRAY | CH_LAST},
+};
diff --git a/src/group_gui.cpp b/src/group_gui.cpp
new file mode 100644
index 000000000..8a486d644
--- /dev/null
+++ b/src/group_gui.cpp
@@ -0,0 +1,795 @@
+/* $Id$ */
+
+/** @file group_gui.cpp */
+
+#include "stdafx.h"
+#include "openttd.h"
+#include "functions.h"
+#include "table/strings.h"
+#include "table/sprites.h"
+#include "window.h"
+#include "gui.h"
+#include "gfx.h"
+#include "vehicle.h"
+#include "command.h"
+#include "engine.h"
+#include "vehicle_gui.h"
+#include "depot.h"
+#include "train.h"
+#include "date.h"
+#include "group.h"
+#include "helpers.hpp"
+#include "viewport.h"
+#include "strings.h"
+#include "debug.h"
+
+
+struct Sorting {
+ Listing aircraft;
+ Listing roadveh;
+ Listing ship;
+ Listing train;
+};
+
+static Sorting _sorting;
+
+
+static void BuildGroupList(grouplist_d* gl, PlayerID owner, VehicleType vehicle_type)
+{
+ const Group** list;
+ const Group *g;
+ uint n = 0;
+
+ if (!(gl->l.flags & VL_REBUILD)) return;
+
+ list = MallocT<const Group*>(GetGroupArraySize());
+ if (list == NULL) {
+ error("Could not allocate memory for the group-sorting-list");
+ }
+
+ FOR_ALL_GROUPS(g) {
+ if (g->owner == owner && g->vehicle_type == vehicle_type) list[n++] = g;
+ }
+
+ free((void*)gl->sort_list);
+ gl->sort_list = MallocT<const Group *>(n);
+ if (n != 0 && gl->sort_list == NULL) {
+ error("Could not allocate memory for the group-sorting-list");
+ }
+ gl->l.list_length = n;
+
+ for (uint i = 0; i < n; ++i) gl->sort_list[i] = list[i];
+ free((void*)list);
+
+ gl->l.flags &= ~VL_REBUILD;
+ gl->l.flags |= VL_RESORT;
+}
+
+
+static int CDECL GroupNameSorter(const void *a, const void *b)
+{
+ static const Group *last_group[2] = { NULL, NULL };
+ static char last_name[2][64] = { "", "" };
+
+ const Group *ga = *(const Group**)a;
+ const Group *gb = *(const Group**)b;
+ int r;
+
+ if (ga != last_group[0]) {
+ last_group[0] = ga;
+ SetDParam(0, ga->index);
+ GetString(last_name[0], ga->string_id, lastof(last_name[0]));
+ }
+
+ if (gb != last_group[1]) {
+ last_group[1] = gb;
+ SetDParam(0, gb->index);
+ GetString(last_name[1], gb->string_id, lastof(last_name[1]));
+ }
+
+ r = strcmp(last_name[0], last_name[1]); // sort by name
+
+ if (r == 0) return ga->index - gb->index;
+
+ return r;
+}
+
+
+static void SortGroupList(grouplist_d *gl)
+{
+ if (!(gl->l.flags & VL_RESORT)) return;
+
+ qsort((void*)gl->sort_list, gl->l.list_length, sizeof(gl->sort_list[0]), GroupNameSorter);
+
+ gl->l.resort_timer = DAY_TICKS * PERIODIC_RESORT_DAYS;
+ gl->l.flags &= ~VL_RESORT;
+}
+
+
+enum GroupListWidgets {
+ GRP_WIDGET_CLOSEBOX = 0,
+ GRP_WIDGET_CAPTION,
+ GRP_WIDGET_STICKY,
+ GRP_WIDGET_EMPTY_TOP_LEFT,
+ GRP_WIDGET_ALL_VEHICLES,
+ GRP_WIDGET_LIST_GROUP,
+ GRP_WIDGET_LIST_GROUP_SCROLLBAR,
+ GRP_WIDGET_SORT_BY_ORDER,
+ GRP_WIDGET_SORT_BY_TEXT,
+ GRP_WIDGET_SORT_BY_DROPDOWN,
+ GRP_WIDGET_EMPTY_TOP_RIGHT,
+ GRP_WIDGET_LIST_VEHICLE,
+ GRP_WIDGET_LIST_VEHICLE_SCROLLBAR,
+ GRP_WIDGET_CREATE_GROUP,
+ GRP_WIDGET_DELETE_GROUP,
+ GRP_WIDGET_RENAME_GROUP,
+ GRP_WIDGET_EMPTY1,
+ GRP_WIDGET_REPLACE_PROTECTION,
+ GRP_WIDGET_EMPTY2,
+ GRP_WIDGET_AVAILABLE_VEHICLES,
+ GRP_WIDGET_MANAGE_VEHICLES,
+ GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN,
+ GRP_WIDGET_STOP_ALL,
+ GRP_WIDGET_START_ALL,
+ GRP_WIDGET_EMPTY_BOTTOM_RIGHT,
+ GRP_WIDGET_RESIZE,
+};
+
+
+static const Widget _group_widgets[] = {
+{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW},
+{ WWT_CAPTION, RESIZE_RIGHT, 14, 11, 513, 0, 13, 0x0, STR_018C_WINDOW_TITLE_DRAG_THIS},
+{ WWT_STICKYBOX, RESIZE_LR, 14, 514, 525, 0, 13, 0x0, STR_STICKY_BUTTON},
+{ WWT_PANEL, RESIZE_NONE, 14, 0, 200, 14, 25, 0x0, STR_NULL},
+{ WWT_PANEL, RESIZE_NONE, 14, 0, 200, 26, 39, 0x0, STR_NULL},
+{ WWT_MATRIX, RESIZE_BOTTOM, 14, 0, 188, 39, 220, 0x701, STR_GROUPS_CLICK_ON_GROUP_FOR_TIP},
+{ WWT_SCROLLBAR, RESIZE_BOTTOM, 14, 189, 200, 26, 220, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST},
+{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 201, 281, 14, 25, STR_SORT_BY, STR_SORT_ORDER_TIP},
+{ WWT_PANEL, RESIZE_NONE, 14, 282, 435, 14, 25, 0x0, STR_SORT_CRITERIA_TIP},
+{ WWT_TEXTBTN, RESIZE_NONE, 14, 436, 447, 14, 25, STR_0225, STR_SORT_CRITERIA_TIP},
+{ WWT_PANEL, RESIZE_RIGHT, 14, 448, 525, 14, 25, 0x0, STR_NULL},
+{ WWT_MATRIX, RESIZE_RB, 14, 201, 513, 26, 233, 0x701, STR_NULL},
+{ WWT_SCROLL2BAR, RESIZE_LRB, 14, 514, 525, 26, 233, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST},
+{ WWT_PUSHIMGBTN, RESIZE_TB, 14, 0, 23, 221, 245, 0x0, STR_GROUP_CREATE_TIP},
+{ WWT_PUSHIMGBTN, RESIZE_TB, 14, 24, 47, 221, 245, 0x0, STR_GROUP_DELETE_TIP},
+{ WWT_PUSHIMGBTN, RESIZE_TB, 14, 48, 71, 221, 245, 0x0, STR_GROUP_RENAME_TIP},
+{ WWT_PANEL, RESIZE_TB, 14, 72, 164, 221, 245, 0x0, STR_NULL},
+{ WWT_PUSHIMGBTN, RESIZE_TB, 14, 165, 188, 221, 245, 0x0, STR_GROUP_REPLACE_PROTECTION_TIP},
+{ WWT_PANEL, RESIZE_TB, 14, 189, 200, 221, 245, 0x0, STR_NULL},
+{ WWT_PUSHTXTBTN, RESIZE_TB, 14, 201, 306, 234, 245, 0x0, STR_AVAILABLE_ENGINES_TIP},
+{ WWT_TEXTBTN, RESIZE_TB, 14, 307, 411, 234, 245, STR_MANAGE_LIST, STR_MANAGE_LIST_TIP},
+{ WWT_TEXTBTN, RESIZE_TB, 14, 412, 423, 234, 245, STR_0225, STR_MANAGE_LIST_TIP},
+{ WWT_PUSHIMGBTN, RESIZE_TB, 14, 424, 435, 234, 245, SPR_FLAG_VEH_STOPPED, STR_MASS_STOP_LIST_TIP},
+{ WWT_PUSHIMGBTN, RESIZE_TB, 14, 436, 447, 234, 245, SPR_FLAG_VEH_RUNNING, STR_MASS_START_LIST_TIP},
+{ WWT_PANEL, RESIZE_RTB, 14, 448, 513, 234, 245, 0x0, STR_NULL},
+{ WWT_RESIZEBOX, RESIZE_LRTB, 14, 514, 525, 234, 245, 0x0, STR_RESIZE_BUTTON},
+{ WIDGETS_END},
+};
+
+
+static void CreateVehicleGroupWindow(Window *w)
+{
+ const PlayerID owner = (PlayerID)GB(w->window_number, 0, 8);
+ groupveh_d *gv = &WP(w, groupveh_d);
+ grouplist_d *gl = &WP(w, groupveh_d).gl;
+
+ w->caption_color = owner;
+ w->hscroll.cap = 10 * 29;
+ w->resize.step_width = 1;
+
+ switch (gv->vehicle_type) {
+ default: NOT_REACHED();
+ case VEH_TRAIN:
+ case VEH_ROAD:
+ w->vscroll.cap = 14;
+ w->vscroll2.cap = 8;
+ w->resize.step_height = PLY_WND_PRC__SIZE_OF_ROW_SMALL;
+ break;
+ case VEH_SHIP:
+ case VEH_AIRCRAFT:
+ w->vscroll.cap = 10;
+ w->vscroll2.cap = 4;
+ w->resize.step_height = PLY_WND_PRC__SIZE_OF_ROW_BIG2;
+ break;
+ }
+
+ w->widget[GRP_WIDGET_LIST_GROUP].data = (w->vscroll.cap << 8) + 1;
+ w->widget[GRP_WIDGET_LIST_VEHICLE].data = (w->vscroll2.cap << 8) + 1;
+
+ switch (gv->vehicle_type) {
+ default: NOT_REACHED(); break;
+ case VEH_TRAIN: gv->_sorting = &_sorting.train; break;
+ case VEH_ROAD: gv->_sorting = &_sorting.roadveh; break;
+ case VEH_SHIP: gv->_sorting = &_sorting.ship; break;
+ case VEH_AIRCRAFT: gv->_sorting = &_sorting.aircraft; break;
+ }
+
+ gv->sort_list = NULL;
+ gv->vehicle_type = (VehicleType)GB(w->window_number, 11, 5);
+ gv->l.sort_type = gv->_sorting->criteria;
+ gv->l.flags = VL_REBUILD | (gv->_sorting->order ? VL_DESC : VL_NONE);
+ gv->l.resort_timer = DAY_TICKS * PERIODIC_RESORT_DAYS; // Set up resort timer
+
+ gl->sort_list = NULL;
+ gl->l.flags = VL_REBUILD | VL_NONE;
+ gl->l.resort_timer = DAY_TICKS * PERIODIC_RESORT_DAYS; // Set up resort timer
+
+ gv->group_sel = DEFAULT_GROUP;
+
+ switch (gv->vehicle_type) {
+ case VEH_TRAIN:
+ w->widget[GRP_WIDGET_LIST_VEHICLE].tooltips = STR_883D_TRAINS_CLICK_ON_TRAIN_FOR;
+ w->widget[GRP_WIDGET_AVAILABLE_VEHICLES].data = STR_AVAILABLE_TRAINS;
+
+ w->widget[GRP_WIDGET_CREATE_GROUP].data = SPR_GROUP_CREATE_TRAIN;
+ w->widget[GRP_WIDGET_RENAME_GROUP].data = SPR_GROUP_RENAME_TRAIN;
+ w->widget[GRP_WIDGET_DELETE_GROUP].data = SPR_GROUP_DELETE_TRAIN;
+ break;
+
+ case VEH_ROAD:
+ w->widget[GRP_WIDGET_LIST_VEHICLE].tooltips = STR_901A_ROAD_VEHICLES_CLICK_ON;
+ w->widget[GRP_WIDGET_AVAILABLE_VEHICLES].data = STR_AVAILABLE_ROAD_VEHICLES;
+
+ w->widget[GRP_WIDGET_CREATE_GROUP].data = SPR_GROUP_CREATE_ROADVEH;
+ w->widget[GRP_WIDGET_RENAME_GROUP].data = SPR_GROUP_RENAME_ROADVEH;
+ w->widget[GRP_WIDGET_DELETE_GROUP].data = SPR_GROUP_DELETE_ROADVEH;
+ break;
+
+ case VEH_SHIP:
+ w->widget[GRP_WIDGET_LIST_VEHICLE].tooltips = STR_9823_SHIPS_CLICK_ON_SHIP_FOR;
+ w->widget[GRP_WIDGET_AVAILABLE_VEHICLES].data = STR_AVAILABLE_SHIPS;
+
+ w->widget[GRP_WIDGET_CREATE_GROUP].data = SPR_GROUP_CREATE_SHIP;
+ w->widget[GRP_WIDGET_RENAME_GROUP].data = SPR_GROUP_RENAME_SHIP;
+ w->widget[GRP_WIDGET_DELETE_GROUP].data = SPR_GROUP_DELETE_SHIP;
+ break;
+
+ case VEH_AIRCRAFT:
+ w->widget[GRP_WIDGET_LIST_VEHICLE].tooltips = STR_A01F_AIRCRAFT_CLICK_ON_AIRCRAFT;
+ w->widget[GRP_WIDGET_AVAILABLE_VEHICLES].data = STR_AVAILABLE_AIRCRAFT;
+
+ w->widget[GRP_WIDGET_CREATE_GROUP].data = SPR_GROUP_CREATE_AIRCRAFT;
+ w->widget[GRP_WIDGET_RENAME_GROUP].data = SPR_GROUP_RENAME_AIRCRAFT;
+ w->widget[GRP_WIDGET_DELETE_GROUP].data = SPR_GROUP_DELETE_AIRCRAFT;
+ break;
+
+ default: NOT_REACHED();
+ }
+}
+
+/**
+ * bitmask for w->window_number
+ * 0-7 PlayerID (owner)
+ * 11-15 vehicle type
+ **/
+static void GroupWndProc(Window *w, WindowEvent *e)
+{
+ const PlayerID owner = (PlayerID)GB(w->window_number, 0, 8);
+ const Player *p = GetPlayer(owner);
+ groupveh_d *gv = &WP(w, groupveh_d);
+ grouplist_d *gl = &WP(w, groupveh_d).gl;
+
+ gv->vehicle_type = (VehicleType)GB(w->window_number, 11, 5);
+
+ switch(e->event) {
+ case WE_CREATE:
+ CreateVehicleGroupWindow(w);
+ break;
+
+ case WE_PAINT: {
+ int x = 203;
+ int y2 = PLY_WND_PRC__OFFSET_TOP_WIDGET;
+ int y1 = PLY_WND_PRC__OFFSET_TOP_WIDGET + 2;
+ int max;
+ int i;
+
+ /* If we select the default group, gv->list will contain all vehicles of the player
+ * else gv->list will contain all vehicles which belong to the selected group */
+ BuildVehicleList(gv, owner, gv->group_sel, IsDefaultGroupID(gv->group_sel) ? VLW_STANDARD : VLW_GROUP_LIST);
+ SortVehicleList(gv);
+
+
+ BuildGroupList(gl, owner, gv->vehicle_type);
+ SortGroupList(gl);
+
+ SetVScrollCount(w, gl->l.list_length);
+ SetVScroll2Count(w, gv->l.list_length);
+
+ /* Disable all lists management button when the list is empty */
+ SetWindowWidgetsDisabledState(w, gv->l.list_length == 0,
+ GRP_WIDGET_STOP_ALL,
+ GRP_WIDGET_START_ALL,
+ GRP_WIDGET_MANAGE_VEHICLES,
+ GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN,
+ WIDGET_LIST_END);
+
+ /* Disable the group specific function when we select the default group */
+ SetWindowWidgetsDisabledState(w, IsDefaultGroupID(gv->group_sel),
+ GRP_WIDGET_DELETE_GROUP,
+ GRP_WIDGET_RENAME_GROUP,
+ GRP_WIDGET_REPLACE_PROTECTION,
+ WIDGET_LIST_END);
+
+ /* If selected_group == DEFAULT_GROUP, draw the standard caption
+ We list all vehicles */
+ if (IsDefaultGroupID(gv->group_sel)) {
+ SetDParam(0, p->name_1);
+ SetDParam(1, p->name_2);
+ SetDParam(2, gv->l.list_length);
+
+ switch (gv->vehicle_type) {
+ case VEH_TRAIN:
+ w->widget[GRP_WIDGET_CAPTION].data = STR_881B_TRAINS;
+ w->widget[GRP_WIDGET_REPLACE_PROTECTION].data = SPR_GROUP_REPLACE_OFF_TRAIN;
+ break;
+ case VEH_ROAD:
+ w->widget[GRP_WIDGET_CAPTION].data = STR_9001_ROAD_VEHICLES;
+ w->widget[GRP_WIDGET_REPLACE_PROTECTION].data = SPR_GROUP_REPLACE_OFF_ROADVEH;
+ break;
+ case VEH_SHIP:
+ w->widget[GRP_WIDGET_CAPTION].data = STR_9805_SHIPS;
+ w->widget[GRP_WIDGET_REPLACE_PROTECTION].data = SPR_GROUP_REPLACE_OFF_SHIP;
+ break;
+ case VEH_AIRCRAFT:
+ w->widget[GRP_WIDGET_CAPTION].data = STR_A009_AIRCRAFT;
+ w->widget[GRP_WIDGET_REPLACE_PROTECTION].data = SPR_GROUP_REPLACE_OFF_AIRCRAFT;
+ break;
+ default: NOT_REACHED(); break;
+ }
+ } else {
+ const Group *g = GetGroup(gv->group_sel);
+
+ SetDParam(0, g->index);
+ SetDParam(1, g->num_vehicle);
+
+ switch (gv->vehicle_type) {
+ case VEH_TRAIN:
+ w->widget[GRP_WIDGET_CAPTION].data = STR_GROUP_TRAINS_CAPTION;
+ w->widget[GRP_WIDGET_REPLACE_PROTECTION].data = (g->replace_protection) ? SPR_GROUP_REPLACE_ON_TRAIN : SPR_GROUP_REPLACE_OFF_TRAIN;
+ break;
+ case VEH_ROAD:
+ w->widget[GRP_WIDGET_CAPTION].data = STR_GROUP_ROADVEH_CAPTION;
+ w->widget[GRP_WIDGET_REPLACE_PROTECTION].data = (g->replace_protection) ? SPR_GROUP_REPLACE_ON_ROADVEH : SPR_GROUP_REPLACE_OFF_ROADVEH;
+ break;
+ case VEH_SHIP:
+ w->widget[GRP_WIDGET_CAPTION].data = STR_GROUP_SHIPS_CAPTION;
+ w->widget[GRP_WIDGET_REPLACE_PROTECTION].data = (g->replace_protection) ? SPR_GROUP_REPLACE_ON_SHIP : SPR_GROUP_REPLACE_OFF_SHIP;
+ break;
+ case VEH_AIRCRAFT:
+ w->widget[GRP_WIDGET_CAPTION].data = STR_GROUP_AIRCRAFTS_CAPTION;
+ w->widget[GRP_WIDGET_REPLACE_PROTECTION].data = (g->replace_protection) ? SPR_GROUP_REPLACE_ON_AIRCRAFT : SPR_GROUP_REPLACE_OFF_AIRCRAFT;
+ break;
+ default: NOT_REACHED(); break;
+ }
+ }
+
+
+ DrawWindowWidgets(w);
+
+ /* Draw Matrix Group
+ * The selected group is drawn in white */
+ StringID str;
+
+ switch (gv->vehicle_type) {
+ case VEH_TRAIN: str = STR_GROUP_ALL_TRAINS; break;
+ case VEH_ROAD: str = STR_GROUP_ALL_ROADS; break;
+ case VEH_SHIP: str = STR_GROUP_ALL_SHIPS; break;
+ case VEH_AIRCRAFT: str = STR_GROUP_ALL_AIRCRAFTS; break;
+ default: NOT_REACHED(); break;
+ }
+ DrawString(10, y1, str, IsDefaultGroupID(gv->group_sel) ? 12 : 16);
+
+ max = min(w->vscroll.pos + w->vscroll.cap, gl->l.list_length);
+ for (i = w->vscroll.pos ; i < max ; ++i) {
+ const Group *g = gl->sort_list[i];
+
+ assert(g->owner == owner);
+
+ y1 += PLY_WND_PRC__SIZE_OF_ROW_TINY;
+
+ /* draw the selected group in white, else we draw it in black */
+ SetDParam(0, g->index);
+ DrawString(10, y1, STR_SV_GROUP_NAME, (gv->group_sel == g->index) ? 12 : 16);
+
+ /* draw the number of vehicles of the group */
+ SetDParam(0, g->num_vehicle);
+ DrawStringRightAligned(187, y1 + 1, STR_GROUP_TINY_NUM, (gv->group_sel == g->index) ? 12 : 16);
+ }
+
+ /* Draw Matrix Vehicle according to the vehicle list built before */
+ DrawString(285, 15, _vehicle_sort_listing[gv->l.sort_type], 0x10);
+ DoDrawString(gv->l.flags & VL_DESC ? DOWNARROW : UPARROW, 269, 15, 0x10);
+
+ max = min(w->vscroll2.pos + w->vscroll2.cap, gv->l.list_length);
+ for (i = w->vscroll2.pos ; i < max ; ++i) {
+ const Vehicle* v = gv->sort_list[i];
+ StringID str;
+
+ assert(v->type == gv->vehicle_type && v->owner == owner);
+
+ DrawVehicleImage(v, x + 19, y2 + 6, w->hscroll.cap, 0, gv->vehicle_sel);
+ DrawVehicleProfitButton(v, x, y2 + 13);
+
+ if (IsVehicleInDepot(v)) {
+ str = STR_021F;
+ } else {
+ str = v->age > v->max_age - 366 ? STR_00E3 : STR_00E2;
+ }
+ SetDParam(0, v->unitnumber);
+ DrawString(x, y2 + 2, str, 0);
+
+ if (w->resize.step_height == PLY_WND_PRC__SIZE_OF_ROW_BIG2) DrawSmallOrderList(v, x + 138, y2);
+
+ if (v->profit_this_year < 0) {
+ str = v->profit_last_year < 0 ?
+ STR_PROFIT_BAD_THIS_YEAR_BAD_LAST_YEAR :
+ STR_PROFIT_BAD_THIS_YEAR_GOOD_LAST_YEAR;
+ } else {
+ str = v->profit_last_year < 0 ?
+ STR_PROFIT_GOOD_THIS_YEAR_BAD_LAST_YEAR :
+ STR_PROFIT_GOOD_THIS_YEAR_GOOD_LAST_YEAR;
+ }
+
+ SetDParam(0, v->profit_this_year);
+ SetDParam(1, v->profit_last_year);
+ DrawString(x + 19, y2 + w->resize.step_height - 8, str, 0);
+
+ if (IsValidGroupID(v->group_id)) {
+ SetDParam(0, v->group_id);
+ DrawString(x + 19, y2, STR_GROUP_TINY_NAME, 16);
+ }
+
+ y2 += w->resize.step_height;
+ }
+
+ break;
+ }
+
+ case WE_CLICK:
+ switch(e->we.click.widget) {
+ case GRP_WIDGET_SORT_BY_ORDER: // Flip sorting method ascending/descending
+ gv->l.flags ^= VL_DESC;
+ gv->l.flags |= VL_RESORT;
+
+ gv->_sorting->order = !!(gv->l.flags & VL_DESC);
+ SetWindowDirty(w);
+ break;
+
+ case GRP_WIDGET_SORT_BY_TEXT:
+ case GRP_WIDGET_SORT_BY_DROPDOWN: // Select sorting criteria dropdown menu
+ ShowDropDownMenu(w, _vehicle_sort_listing, gv->l.sort_type, GRP_WIDGET_SORT_BY_DROPDOWN, 0, 0);
+ return;
+
+ case GRP_WIDGET_ALL_VEHICLES: // All vehicles button
+ if (!IsDefaultGroupID(gv->group_sel)) {
+ gv->group_sel = DEFAULT_GROUP;
+ gv->l.flags |= VL_REBUILD;
+ SetWindowDirty(w);
+ }
+ break;
+
+ case GRP_WIDGET_LIST_GROUP: { // Matrix Group
+ uint16 id_g = (e->we.click.pt.y - PLY_WND_PRC__OFFSET_TOP_WIDGET - 13) / PLY_WND_PRC__SIZE_OF_ROW_TINY;
+
+ if (id_g >= w->vscroll.cap) return;
+
+ id_g += w->vscroll.pos;
+
+ if (id_g >= gl->l.list_length) return;
+
+ gv->group_sel = gl->sort_list[id_g]->index;;
+
+ gv->l.flags |= VL_REBUILD;
+ SetWindowDirty(w);
+ break;
+ }
+
+ case GRP_WIDGET_LIST_VEHICLE: { // Matrix Vehicle
+ uint32 id_v = (e->we.click.pt.y - PLY_WND_PRC__OFFSET_TOP_WIDGET) / (int)w->resize.step_height;
+ const Vehicle *v;
+
+ if (id_v >= w->vscroll2.cap) return; // click out of bounds
+
+ id_v += w->vscroll2.pos;
+
+ if (id_v >= gv->l.list_length) return; // click out of list bound
+
+ v = gv->sort_list[id_v];
+
+ gv->vehicle_sel = v->index;
+
+ if (IsValidVehicle(v)) {
+ CursorID image;
+
+ switch (gv->vehicle_type) {
+ case VEH_TRAIN: image = GetTrainImage(v, DIR_W); break;
+ case VEH_ROAD: image = GetRoadVehImage(v, DIR_W); break;
+ case VEH_SHIP: image = GetShipImage(v, DIR_W); break;
+ case VEH_AIRCRAFT: image = GetAircraftImage(v, DIR_W); break;
+ default: NOT_REACHED(); break;
+ }
+
+ SetObjectToPlaceWnd(image, GetVehiclePalette(v), 4, w);
+ }
+
+ SetWindowDirty(w);
+ break;
+ }
+
+ case GRP_WIDGET_CREATE_GROUP: // Create a new group
+ if (!CmdFailed(DoCommandP(0, gv->vehicle_type, 0, NULL, CMD_CREATE_GROUP | CMD_MSG(STR_GROUP_CAN_T_CREATE)))) {
+ SetWindowDirty(w);
+ gl->l.flags |= VL_REBUILD;
+ }
+ break;
+
+ case GRP_WIDGET_DELETE_GROUP: // Delete the selected group
+ if (!CmdFailed(DoCommandP(0, gv->group_sel, 0, NULL, CMD_DELETE_GROUP | CMD_MSG(STR_GROUP_CAN_T_DELETE)))) {
+ gv->group_sel = DEFAULT_GROUP;
+ gv->l.flags |= VL_REBUILD;
+ gl->l.flags |= VL_REBUILD;
+ SetWindowDirty(w);
+ }
+ break;
+
+ case GRP_WIDGET_RENAME_GROUP: { // Rename the selected roup
+ assert(!IsDefaultGroupID(gv->group_sel));
+
+ const Group *g = GetGroup(gv->group_sel);
+
+ SetDParam(0, g->index);
+ ShowQueryString(g->string_id, STR_GROUP_RENAME_CAPTION, 31, 150, w, CS_ALPHANUMERAL);
+ } break;
+
+
+ case GRP_WIDGET_AVAILABLE_VEHICLES:
+ ShowBuildVehicleWindow(0, gv->vehicle_type);
+ break;
+
+ case GRP_WIDGET_MANAGE_VEHICLES:
+ case GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN: {
+ static StringID action_str[] = {
+ STR_REPLACE_VEHICLES,
+ STR_SEND_FOR_SERVICING,
+ STR_SEND_TRAIN_TO_DEPOT,
+ STR_NULL,
+ STR_NULL,
+ INVALID_STRING_ID
+ };
+
+ action_str[3] = IsDefaultGroupID(gv->group_sel) ? INVALID_STRING_ID : STR_GROUP_ADD_SHARED_VEHICLE;
+ action_str[4] = IsDefaultGroupID(gv->group_sel) ? INVALID_STRING_ID : STR_GROUP_REMOVE_ALL_VEHICLES;
+
+ ShowDropDownMenu(w, action_str, 0, GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN, 0, 0);
+ break;
+ }
+
+
+ case GRP_WIDGET_START_ALL:
+ case GRP_WIDGET_STOP_ALL: { // Start/stop all vehicles of the list
+ DoCommandP(0, gv->group_sel, ((IsDefaultGroupID(gv->group_sel) ? VLW_STANDARD : VLW_GROUP_LIST) & VLW_MASK)
+ | (1 << 6)
+ | (e->we.click.widget == GRP_WIDGET_START_ALL ? (1 << 5) : 0)
+ | gv->vehicle_type, NULL, CMD_MASS_START_STOP);
+
+ break;
+ }
+
+ case GRP_WIDGET_REPLACE_PROTECTION:
+ if (!IsDefaultGroupID(gv->group_sel)) {
+ Group *g = GetGroup(gv->group_sel);
+
+ g->replace_protection = !g->replace_protection;
+ }
+ break;
+ }
+
+ break;
+
+ case WE_DRAGDROP: {
+ switch (e->we.click.widget) {
+ case GRP_WIDGET_ALL_VEHICLES: // All trains
+ if (!CmdFailed(DoCommandP(0, DEFAULT_GROUP , gv->vehicle_sel, NULL, CMD_ADD_VEHICLE_GROUP | CMD_MSG(STR_GROUP_CAN_T_ADD_VEHICLE)))) {
+ gv->l.flags |= VL_REBUILD;
+ }
+
+ gv->vehicle_sel = INVALID_VEHICLE;
+
+ SetWindowDirty(w);
+
+ break;
+
+ case GRP_WIDGET_LIST_GROUP: { // Maxtrix group
+ uint16 id_g = (e->we.click.pt.y - PLY_WND_PRC__OFFSET_TOP_WIDGET - 13) / PLY_WND_PRC__SIZE_OF_ROW_TINY;
+ const VehicleID vindex = gv->vehicle_sel;
+
+ gv->vehicle_sel = INVALID_VEHICLE;
+
+ SetWindowDirty(w);
+
+ if (id_g >= w->vscroll.cap) return;
+
+ id_g += w->vscroll.pos;
+
+ if (id_g >= gl->l.list_length) return;
+
+ if (!CmdFailed(DoCommandP(0, gl->sort_list[id_g]->index , vindex, NULL, CMD_ADD_VEHICLE_GROUP | CMD_MSG(STR_GROUP_CAN_T_ADD_VEHICLE)))) {
+ gv->l.flags |= VL_REBUILD;
+ }
+
+ break;
+ }
+
+ case GRP_WIDGET_LIST_VEHICLE: { // Maxtrix vehicle
+ uint32 id_v = (e->we.click.pt.y - PLY_WND_PRC__OFFSET_TOP_WIDGET) / (int)w->resize.step_height;
+ const Vehicle *v;
+ const VehicleID vindex = gv->vehicle_sel;
+
+ gv->vehicle_sel = INVALID_VEHICLE;
+
+ SetWindowDirty(w);
+
+ if (id_v >= w->vscroll2.cap) return; // click out of bounds
+
+ id_v += w->vscroll2.pos;
+
+ if (id_v >= gv->l.list_length) return; // click out of list bound
+
+ v = gv->sort_list[id_v];
+
+ if (vindex == v->index) {
+ switch (gv->vehicle_type) {
+ default: NOT_REACHED(); break;
+ case VEH_TRAIN: ShowTrainViewWindow(v); break;
+ case VEH_ROAD: ShowRoadVehViewWindow(v); break;
+ case VEH_SHIP: ShowShipViewWindow(v); break;
+ case VEH_AIRCRAFT: ShowAircraftViewWindow(v); break;
+ }
+ }
+
+ break;
+ }
+ }
+ break;
+ }
+
+ case WE_ON_EDIT_TEXT:
+ if (!StrEmpty(e->we.edittext.str)) {
+ _cmd_text = e->we.edittext.str;
+
+ if (!CmdFailed(DoCommandP(0, gv->group_sel, 0, NULL, CMD_RENAME_GROUP | CMD_MSG(STR_GROUP_CAN_T_RENAME)))) {
+ SetWindowDirty(w);
+ gl->l.flags |= VL_REBUILD;
+ }
+ }
+ break;
+
+ case WE_RESIZE:
+ w->hscroll.cap += e->we.sizing.diff.x;
+ w->vscroll.cap += e->we.sizing.diff.y / PLY_WND_PRC__SIZE_OF_ROW_TINY;
+ w->vscroll2.cap += e->we.sizing.diff.y / (int)w->resize.step_height;
+
+ w->widget[GRP_WIDGET_LIST_GROUP].data = (w->vscroll.cap << 8) + 1;
+ w->widget[GRP_WIDGET_LIST_VEHICLE].data = (w->vscroll2.cap << 8) + 1;
+ break;
+
+
+ case WE_DROPDOWN_SELECT: // we have selected a dropdown item in the list
+ switch (e->we.dropdown.button) {
+ case GRP_WIDGET_SORT_BY_DROPDOWN:
+ if (gv->l.sort_type != e->we.dropdown.index) {
+ gv->l.flags |= VL_RESORT;
+ gv->l.sort_type = e->we.dropdown.index;
+ gv->_sorting->criteria = gv->l.sort_type;
+ }
+ break;
+
+ case GRP_WIDGET_MANAGE_VEHICLES_DROPDOWN:
+ assert(gv->l.list_length != 0);
+
+ switch (e->we.dropdown.index) {
+ case 0: // Replace window
+ ShowReplaceGroupVehicleWindow(gv->group_sel, gv->vehicle_type);
+ break;
+ case 1: // Send for servicing
+ DoCommandP(0, gv->group_sel, ((IsDefaultGroupID(gv->group_sel) ? VLW_STANDARD : VLW_GROUP_LIST) & VLW_MASK)
+ | DEPOT_MASS_SEND
+ | DEPOT_SERVICE, NULL, GetCmdSendToDepot(gv->vehicle_type));
+ break;
+ case 2: // Send to Depots
+ DoCommandP(0, gv->group_sel, ((IsDefaultGroupID(gv->group_sel) ? VLW_STANDARD : VLW_GROUP_LIST) & VLW_MASK)
+ | DEPOT_MASS_SEND, NULL, GetCmdSendToDepot(gv->vehicle_type));
+ break;
+ case 3: // Add shared Vehicles
+ assert(!IsDefaultGroupID(gv->group_sel));
+
+ if (!CmdFailed(DoCommandP(0, gv->group_sel, gv->vehicle_type, NULL, CMD_ADD_SHARED_VEHICLE_GROUP | CMD_MSG(STR_GROUP_CAN_T_ADD_SHARED_VEHICLE)))) {
+ gv->l.flags |= VL_REBUILD;
+ }
+ break;
+ case 4: // Remove all Vehicles from the selected group
+ assert(!IsDefaultGroupID(gv->group_sel));
+
+ if (!CmdFailed(DoCommandP(0, gv->group_sel, gv->vehicle_type, NULL, CMD_REMOVE_ALL_VEHICLES_GROUP | CMD_MSG(STR_GROUP_CAN_T_REMOVE_ALL_VEHICLES)))) {
+ gv->l.flags |= VL_REBUILD;
+ }
+ break;
+ default: NOT_REACHED();
+ }
+ break;
+
+ default: NOT_REACHED();
+ }
+
+ SetWindowDirty(w);
+ break;
+
+
+ case WE_DESTROY:
+ free((void*)gv->sort_list);
+ free((void*)gl->sort_list);
+ break;
+
+
+ case WE_TICK: // resort the lists every 20 seconds orso (10 days)
+ if (--gv->l.resort_timer == 0) {
+ gv->l.resort_timer = DAY_TICKS * PERIODIC_RESORT_DAYS;
+ gv->l.flags |= VL_RESORT;
+ SetWindowDirty(w);
+ }
+ if (--gl->l.resort_timer == 0) {
+ gl->l.resort_timer = DAY_TICKS * PERIODIC_RESORT_DAYS;
+ gl->l.flags |= VL_RESORT;
+ SetWindowDirty(w);
+ }
+ break;
+ }
+}
+
+
+static const WindowDesc _group_desc = {
+ WDP_AUTO, WDP_AUTO, 526, 246,
+ WC_TRAINS_LIST, WC_NONE,
+ WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE,
+ _group_widgets,
+ GroupWndProc
+};
+
+void ShowPlayerGroup(PlayerID player, VehicleType vehicle_type)
+{
+ WindowClass wc;
+
+ switch (vehicle_type) {
+ default: NOT_REACHED();
+ case VEH_TRAIN: wc = WC_TRAINS_LIST; break;
+ case VEH_ROAD: wc = WC_ROADVEH_LIST; break;
+ case VEH_SHIP: wc = WC_SHIPS_LIST; break;
+ case VEH_AIRCRAFT: wc = WC_AIRCRAFT_LIST; break;
+ }
+
+ WindowNumber num = (vehicle_type << 11) | VLW_GROUP_LIST | player;
+ DeleteWindowById(wc, num);
+ Window *w = AllocateWindowDescFront(&_group_desc, num);
+ if (w == NULL) return;
+
+ w->window_class = wc;
+
+ switch (vehicle_type) {
+ default: NOT_REACHED();
+ case VEH_ROAD:
+ ResizeWindow(w, -66, 0);
+ /* FALL THROUGH */
+ case VEH_TRAIN:
+ w->resize.height = w->height - (PLY_WND_PRC__SIZE_OF_ROW_SMALL * 4); // Minimum of 4 vehicles
+ break;
+
+ case VEH_SHIP:
+ case VEH_AIRCRAFT:
+ ResizeWindow(w, -66, -52);
+ w->resize.height = w->height; // Minimum of 4 vehicles
+ break;
+ }
+
+ /* Set the minimum window size to the current window size */
+ w->resize.width = w->width;
+}
diff --git a/src/gui.h b/src/gui.h
index 71be707a6..d2f3be76f 100644
--- a/src/gui.h
+++ b/src/gui.h
@@ -139,4 +139,6 @@ VARDEF PlaceProc *_place_proc;
/* vehicle_gui.cpp */
void InitializeGUI();
+void ShowPlayerGroup(PlayerID player, VehicleType veh);
+
#endif /* GUI_H */
diff --git a/src/lang/english.txt b/src/lang/english.txt
index b10e857b1..d9ada440b 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -1098,6 +1098,7 @@ STR_CONFIG_PATCHES_SCROLLWHEEL_SCROLL :Scroll map
STR_CONFIG_PATCHES_SCROLLWHEEL_OFF :Off
STR_CONFIG_PATCHES_SCROLLWHEEL_MULTIPLIER :{LTBLUE}Map scrollwheel speed: {ORANGE}{STRING1}
STR_CONFIG_PATCHES_PAUSE_ON_NEW_GAME :{LTBLUE}Automatically pause when starting a new game: {ORANGE}{STRING1}
+STR_CONFIG_PATCHES_ADVANCED_VEHICLE_LISTS :{LTBLUE}Use the advanced vehicle list: {ORANGE}{STRING1}
STR_CONFIG_PATCHES_MAX_TRAINS :{LTBLUE}Max trains per player: {ORANGE}{STRING1}
STR_CONFIG_PATCHES_MAX_ROADVEH :{LTBLUE}Max road vehicles per player: {ORANGE}{STRING1}
@@ -2012,6 +2013,8 @@ STR_SV_STNAME_LOWER :Lower {STRING1}
STR_SV_STNAME_HELIPORT :{STRING1} Heliport
STR_SV_STNAME_FOREST :{STRING1} Forest
+STR_SV_GROUP_NAME :{GROUP}
+
############ end of savegame specific region!
##id 0x6800
@@ -3187,3 +3190,41 @@ STR_TRANSPARENT_INDUSTRIES_DESC :{BLACK}Toggle t
STR_TRANSPARENT_BUILDINGS_DESC :{BLACK}Toggle transparency for buildables like stations, depots, waypoints and catenary
STR_TRANSPARENT_BRIDGES_DESC :{BLACK}Toggle transparency for bridges
STR_TRANSPARENT_STRUCTURES_DESC :{BLACK}Toggle transparency for structures like lighthouses and antennas, maybe in future for eyecandy
+
+##### Mass Order
+STR_GROUP_NAME_FORMAT :Group {COMMA}
+STR_GROUP_TINY_NAME :{TINYFONT}{GROUP}
+STR_GROUP_ALL_TRAINS :All trains
+STR_GROUP_ALL_ROADS :All road vehicles
+STR_GROUP_ALL_SHIPS :All ships
+STR_GROUP_ALL_AIRCRAFTS :All aircraft
+STR_GROUP_TINY_NUM :{TINYFONT}{COMMA}
+STR_GROUP_ADD_SHARED_VEHICLE :Add shared vehicles
+STR_GROUP_REMOVE_ALL_VEHICLES :Remove all vehicles
+
+STR_GROUP_TRAINS_CAPTION :{WHITE}{GROUP} - {COMMA} Train{P "" s}
+STR_GROUP_ROADVEH_CAPTION :{WHITE}{GROUP} - {COMMA} Road Vehicle{P "" s}
+STR_GROUP_SHIPS_CAPTION :{WHITE}{GROUP} - {COMMA} Ship{P "" s}
+STR_GROUP_AIRCRAFTS_CAPTION :{WHITE}{GROUP} - {COMMA} Aircraft
+STR_GROUP_RENAME_CAPTION :{BLACK}Rename a group
+STR_GROUP_REPLACE_CAPTION :{WHITE}Replace Vehicles of "{GROUP}"
+
+STR_GROUP_CAN_T_CREATE :{WHITE}Can't create group...
+STR_GROUP_CAN_T_DELETE :{WHITE}Can't delete this group...
+STR_GROUP_CAN_T_RENAME :{WHITE}Can't rename group...
+STR_GROUP_CAN_T_REMOVE_ALL_VEHICLES :{WHITE}Can't remove all vehicles from this group...
+STR_GROUP_CAN_T_ADD_VEHICLE :{WHITE}Can't add the vehicle to this group...
+STR_GROUP_CAN_T_ADD_SHARED_VEHICLE :{WHITE}Can't add shared vehicles to group...
+
+STR_GROUPS_CLICK_ON_GROUP_FOR_TIP :{BLACK}Groups - Click on a group to list all vehicles of this group
+STR_GROUP_CREATE_TIP :{BLACK}Click to create a group
+STR_GROUP_DELETE_TIP :{BLACK}Delete the selected group
+STR_GROUP_RENAME_TIP :{BLACK}Rename the selected group
+STR_GROUP_REPLACE_PROTECTION_TIP :{BLACK}Click to protect this group from global autoreplace
+
+STR_PROFIT_GOOD_THIS_YEAR_GOOD_LAST_YEAR :{TINYFONT}{BLACK}Profit this year: {GREEN}{CURRENCY} {BLACK}(last year: {GREEN}{CURRENCY}{BLACK})
+STR_PROFIT_BAD_THIS_YEAR_GOOD_LAST_YEAR :{TINYFONT}{BLACK}Profit this year: {RED}{CURRENCY} {BLACK}(last year: {GREEN}{CURRENCY}{BLACK})
+STR_PROFIT_GOOD_THIS_YEAR_BAD_LAST_YEAR :{TINYFONT}{BLACK}Profit this year: {GREEN}{CURRENCY} {BLACK}(last year: {RED}{CURRENCY}{BLACK})
+STR_PROFIT_BAD_THIS_YEAR_BAD_LAST_YEAR :{TINYFONT}{BLACK}Profit this year: {RED}{CURRENCY} {BLACK}(last year: {RED}{CURRENCY}{BLACK})
+
+########
diff --git a/src/misc.cpp b/src/misc.cpp
index 9ea8a738e..2dd2750d0 100644
--- a/src/misc.cpp
+++ b/src/misc.cpp
@@ -22,6 +22,7 @@
#include "newgrf_house.h"
#include "date.h"
#include "cargotype.h"
+#include "group.h"
char _name_array[512][32];
@@ -120,6 +121,7 @@ void InitializeGame(int mode, uint size_x, uint size_y)
InitializeWaypoints();
InitializeDepots();
InitializeOrders();
+ InitializeGroup();
InitNewsItemStructs();
InitializeLandscape();
diff --git a/src/openttd.cpp b/src/openttd.cpp
index a2bb58b43..b4353612b 100644
--- a/src/openttd.cpp
+++ b/src/openttd.cpp
@@ -63,6 +63,7 @@
#include "newgrf_house.h"
#include "newgrf_commons.h"
#include "player_face.h"
+#include "group.h"
#include "bridge_map.h"
#include "clear_map.h"
@@ -294,6 +295,7 @@ static void UnInitializeGame()
CleanPool(&_Vehicle_pool);
CleanPool(&_Sign_pool);
CleanPool(&_Order_pool);
+ CleanPool(&_Group_pool);
free((void*)_town_sort);
free((void*)_industry_sort);
@@ -1954,6 +1956,20 @@ bool AfterLoadGame()
_opt.diff.number_towns++;
}
+ /* Recalculate */
+ Group *g;
+ FOR_ALL_GROUPS(g) {
+ const Vehicle *v;
+ FOR_ALL_VEHICLES(v) {
+ if (!IsEngineCountable(v)) continue;
+
+ if (v->group_id != g->index || v->type != g->vehicle_type || v->owner != g->owner) continue;
+
+ g->num_engines[v->engine_type]++;
+ }
+ }
+
+
return true;
}
diff --git a/src/openttd.h b/src/openttd.h
index f4511ea92..0c66d9275 100644
--- a/src/openttd.h
+++ b/src/openttd.h
@@ -40,6 +40,7 @@ struct Town;
struct NewsItem;
struct Industry;
struct DrawPixelInfo;
+struct Group;
typedef byte VehicleOrderID; ///< The index of an order within its current vehicle (not pool related)
typedef byte CargoID;
typedef byte LandscapeID;
@@ -63,6 +64,7 @@ typedef uint16 DepotID;
typedef uint16 WaypointID;
typedef uint16 OrderID;
typedef uint16 SignID;
+typedef uint16 GroupID;
typedef uint16 EngineRenewID;
typedef uint16 DestinationID;
diff --git a/src/player.h b/src/player.h
index 297bd4970..0732bf868 100644
--- a/src/player.h
+++ b/src/player.h
@@ -311,7 +311,7 @@ static inline void RemoveAllEngineReplacementForPlayer(Player *p) { RemoveAllEng
* @return The engine type to replace with, or INVALID_ENGINE if no
* replacement is in the list.
*/
-static inline EngineID EngineReplacementForPlayer(const Player *p, EngineID engine) { return EngineReplacement(p->engine_renew_list, engine); }
+static inline EngineID EngineReplacementForPlayer(const Player *p, EngineID engine, GroupID group) { return EngineReplacement(p->engine_renew_list, engine, group); }
/**
* Check if a player has a replacement set up for the given engine.
@@ -319,7 +319,7 @@ static inline EngineID EngineReplacementForPlayer(const Player *p, EngineID engi
* @param engine Engine type to be replaced.
* @return true if a replacement was set up, false otherwise.
*/
-static inline bool EngineHasReplacementForPlayer(const Player *p, EngineID engine) { return EngineReplacementForPlayer(p, engine) != INVALID_ENGINE; }
+static inline bool EngineHasReplacementForPlayer(const Player *p, EngineID engine, GroupID group) { return EngineReplacementForPlayer(p, engine, group) != INVALID_ENGINE; }
/**
* Add an engine replacement for the player.
@@ -329,7 +329,7 @@ static inline bool EngineHasReplacementForPlayer(const Player *p, EngineID engin
* @param flags The calling command flags.
* @return 0 on success, CMD_ERROR on failure.
*/
-static inline int32 AddEngineReplacementForPlayer(Player *p, EngineID old_engine, EngineID new_engine, uint32 flags) { return AddEngineReplacement(&p->engine_renew_list, old_engine, new_engine, flags); }
+static inline int32 AddEngineReplacementForPlayer(Player *p, EngineID old_engine, EngineID new_engine, GroupID group, uint32 flags) { return AddEngineReplacement(&p->engine_renew_list, old_engine, new_engine, group, flags); }
/**
* Remove an engine replacement for the player.
@@ -338,7 +338,7 @@ static inline int32 AddEngineReplacementForPlayer(Player *p, EngineID old_engine
* @param flags The calling command flags.
* @return 0 on success, CMD_ERROR on failure.
*/
-static inline int32 RemoveEngineReplacementForPlayer(Player *p, EngineID engine, uint32 flags) {return RemoveEngineReplacement(&p->engine_renew_list, engine, flags); }
+static inline int32 RemoveEngineReplacementForPlayer(Player *p, EngineID engine, GroupID group, uint32 flags) {return RemoveEngineReplacement(&p->engine_renew_list, engine, group, flags); }
/**
* Reset the livery schemes to the player's primary colour.
diff --git a/src/players.cpp b/src/players.cpp
index 1a7487ce9..b142f669b 100644
--- a/src/players.cpp
+++ b/src/players.cpp
@@ -27,6 +27,7 @@
#include "date.h"
#include "window.h"
#include "player_face.h"
+#include "group.h"
/**
* Sets the local player and updates the patch settings that are set on a
@@ -638,6 +639,7 @@ static void DeletePlayerStuff(PlayerID pi)
* if p1 = 2, then
* - p2 = minimum amount of money available
* if p1 = 3, then:
+ * - p1 bits 8-15 = engine group
* - p2 bits 0-15 = old engine type
* - p2 bits 16-31 = new engine type
* if p1 = 4, then:
@@ -693,8 +695,11 @@ int32 CmdSetAutoReplace(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
case 3: {
EngineID old_engine_type = GB(p2, 0, 16);
EngineID new_engine_type = GB(p2, 16, 16);
+ GroupID id_g = GB(p1, 16, 8);
int32 cost;
+ if (!IsValidGroupID(id_g)) return CMD_ERROR;
+
if (new_engine_type != INVALID_ENGINE) {
/* First we make sure that it's a valid type the user requested
* check that it's an engine that is in the engine array */
@@ -714,9 +719,9 @@ int32 CmdSetAutoReplace(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
if (!HASBIT(GetEngine(new_engine_type)->player_avail, _current_player))
return CMD_ERROR;
- cost = AddEngineReplacementForPlayer(p, old_engine_type, new_engine_type, flags);
+ cost = AddEngineReplacementForPlayer(p, old_engine_type, new_engine_type, id_g, flags);
} else {
- cost = RemoveEngineReplacementForPlayer(p, old_engine_type, flags);
+ cost = RemoveEngineReplacementForPlayer(p, old_engine_type,id_g, flags);
}
if (IsLocalPlayer()) InvalidateAutoreplaceWindow(old_engine_type);
@@ -901,6 +906,7 @@ int32 CmdPlayerCtrl(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
p->is_active = false;
}
RemoveAllEngineReplacementForPlayer(p);
+ RemoveAllGroupsForPlayer(p);
} break;
diff --git a/src/saveload.cpp b/src/saveload.cpp
index a4963e745..6550fdfdd 100644
--- a/src/saveload.cpp
+++ b/src/saveload.cpp
@@ -29,7 +29,7 @@
#include <setjmp.h>
#include <list>
-extern const uint16 SAVEGAME_VERSION = 59;
+extern const uint16 SAVEGAME_VERSION = 60;
uint16 _sl_version; ///< the major savegame version identifier
byte _sl_minor_version; ///< the minor savegame version, DO NOT USE!
@@ -1257,6 +1257,7 @@ extern const ChunkHandler _industry_chunk_handlers[];
extern const ChunkHandler _economy_chunk_handlers[];
extern const ChunkHandler _animated_tile_chunk_handlers[];
extern const ChunkHandler _newgrf_chunk_handlers[];
+extern const ChunkHandler _group_chunk_handlers[];
static const ChunkHandler * const _chunk_handlers[] = {
_misc_chunk_handlers,
@@ -1274,6 +1275,7 @@ static const ChunkHandler * const _chunk_handlers[] = {
_player_chunk_handlers,
_animated_tile_chunk_handlers,
_newgrf_chunk_handlers,
+ _group_chunk_handlers,
NULL,
};
diff --git a/src/settings.cpp b/src/settings.cpp
index 203d5e0ff..f71bc862f 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -1342,6 +1342,7 @@ const SettingDesc _patch_settings[] = {
SDT_VAR(Patches, scrollwheel_scrolling,SLE_UINT8,S,MS, 0, 0, 2, 0, STR_CONFIG_PATCHES_SCROLLWHEEL_SCROLLING, NULL),
SDT_VAR(Patches,scrollwheel_multiplier,SLE_UINT8,S, 0, 5, 1, 15, 1, STR_CONFIG_PATCHES_SCROLLWHEEL_MULTIPLIER,NULL),
SDT_BOOL(Patches, pause_on_newgame, S, 0, false, STR_CONFIG_PATCHES_PAUSE_ON_NEW_GAME, NULL),
+ SDT_BOOL(Patches, advanced_vehicle_list, S, 0, true, STR_CONFIG_PATCHES_ADVANCED_VEHICLE_LISTS, NULL),
/***************************************************************************/
/* Construction section of the GUI-configure patches window */
diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp
index f5d846b41..140337f76 100644
--- a/src/settings_gui.cpp
+++ b/src/settings_gui.cpp
@@ -598,6 +598,7 @@ static const char *_patches_ui[] = {
"scrollwheel_scrolling",
"scrollwheel_multiplier",
"pause_on_newgame",
+ "advanced_vehicle_list",
};
static const char *_patches_construction[] = {
diff --git a/src/strgen/strgen.cpp b/src/strgen/strgen.cpp
index e480f6163..221bd0357 100644
--- a/src/strgen/strgen.cpp
+++ b/src/strgen/strgen.cpp
@@ -506,6 +506,7 @@ static const CmdStruct _cmd_structs[] = {
{"WAYPOINT", EmitSingleChar, SCC_WAYPOINT_NAME, 1, 0}, // waypoint name
{"STATION", EmitSingleChar, SCC_STATION_NAME, 1, 0},
{"TOWN", EmitSingleChar, SCC_TOWN_NAME, 1, 0},
+ {"GROUP", EmitSingleChar, SCC_GROUP_NAME, 1, 0},
// 0x9D is used for the pseudo command SETCASE
// 0x9E is used for case switching
diff --git a/src/strings.cpp b/src/strings.cpp
index b0b6fb1a7..eae48281a 100644
--- a/src/strings.cpp
+++ b/src/strings.cpp
@@ -25,6 +25,8 @@
#include "industry.h"
#include "helpers.hpp"
#include "cargotype.h"
+#include "group.h"
+#include "debug.h"
/* for opendir/readdir/closedir */
# include "fios.h"
@@ -840,6 +842,18 @@ static char* FormatString(char* buff, const char* str, const int32* argv, uint c
break;
}
+ case SCC_GROUP_NAME: { // {GROUP}
+ const Group *g = GetGroup(GetInt32(&argv));
+ int32 args[1];
+
+ assert(IsValidGroup(g));
+
+ args[0] = g->index;
+ buff = GetStringWithArgs(buff, (g->string_id == STR_SV_GROUP_NAME) ? (StringID)STR_GROUP_NAME_FORMAT : g->string_id, args, last);
+
+ break;
+ }
+
case SCC_CURRENCY_64: { // {CURRENCY64}
buff = FormatGenericCurrency(buff, _currency, GetInt64(&argv), false, last);
break;
diff --git a/src/table/control_codes.h b/src/table/control_codes.h
index a1ff42a89..6daaee0eb 100644
--- a/src/table/control_codes.h
+++ b/src/table/control_codes.h
@@ -26,6 +26,7 @@ enum {
SCC_WAYPOINT_NAME,
SCC_STATION_NAME,
SCC_TOWN_NAME,
+ SCC_GROUP_NAME,
SCC_CURRENCY_COMPACT,
SCC_CURRENCY_COMPACT_64,
diff --git a/src/table/files.h b/src/table/files.h
index 3a2cb6d1e..79522ab34 100644
--- a/src/table/files.h
+++ b/src/table/files.h
@@ -62,4 +62,5 @@ static MD5File files_openttd[] = {
{ "openttd.grf", { 0x85, 0x4f, 0xf6, 0xb5, 0xd2, 0xf7, 0xbc, 0x1e, 0xb9, 0xdc, 0x44, 0xef, 0x35, 0x5f, 0x64, 0x9b } },
{ "trkfoundw.grf", { 0x12, 0x33, 0x3f, 0xa3, 0xd1, 0x86, 0x8b, 0x04, 0x53, 0x18, 0x9c, 0xee, 0xf9, 0x2d, 0xf5, 0x95 } },
{ "roadstops.grf", { 0x8c, 0xd9, 0x45, 0x21, 0x28, 0x82, 0x96, 0x45, 0x33, 0x22, 0x7a, 0xb9, 0x0d, 0xf3, 0x67, 0x4a } },
+ { "group.grf", { 0xe8, 0x52, 0x5f, 0x1c, 0x3e, 0xf9, 0x91, 0x9d, 0x0f, 0x70, 0x8c, 0x8a, 0x21, 0xa4, 0xc7, 0x02 } },
};
diff --git a/src/table/sprites.h b/src/table/sprites.h
index a1a12e5ad..83d8e9377 100644
--- a/src/table/sprites.h
+++ b/src/table/sprites.h
@@ -128,6 +128,28 @@ enum Sprites {
SPR_TRUCK_STOP_DT_X_W = SPR_ROADSTOP_BASE + 6,
SPR_TRUCK_STOP_DT_X_E = SPR_ROADSTOP_BASE + 7,
+ SPR_GROUP_BASE = SPR_ROADSTOP_BASE + 8, // The sprites used for the group interface
+ SPR_GROUP_CREATE_TRAIN = SPR_GROUP_BASE,
+ SPR_GROUP_CREATE_ROADVEH = SPR_GROUP_BASE + 1,
+ SPR_GROUP_CREATE_SHIP = SPR_GROUP_BASE + 2,
+ SPR_GROUP_CREATE_AIRCRAFT = SPR_GROUP_BASE + 3,
+ SPR_GROUP_DELETE_TRAIN = SPR_GROUP_BASE + 4,
+ SPR_GROUP_DELETE_ROADVEH = SPR_GROUP_BASE + 5,
+ SPR_GROUP_DELETE_SHIP = SPR_GROUP_BASE + 6,
+ SPR_GROUP_DELETE_AIRCRAFT = SPR_GROUP_BASE + 7,
+ SPR_GROUP_RENAME_TRAIN = SPR_GROUP_BASE + 8,
+ SPR_GROUP_RENAME_ROADVEH = SPR_GROUP_BASE + 9,
+ SPR_GROUP_RENAME_SHIP = SPR_GROUP_BASE + 10,
+ SPR_GROUP_RENAME_AIRCRAFT = SPR_GROUP_BASE + 11,
+ SPR_GROUP_REPLACE_ON_TRAIN = SPR_GROUP_BASE + 12,
+ SPR_GROUP_REPLACE_ON_ROADVEH = SPR_GROUP_BASE + 13,
+ SPR_GROUP_REPLACE_ON_SHIP = SPR_GROUP_BASE + 14,
+ SPR_GROUP_REPLACE_ON_AIRCRAFT = SPR_GROUP_BASE + 15,
+ SPR_GROUP_REPLACE_OFF_TRAIN = SPR_GROUP_BASE + 16,
+ SPR_GROUP_REPLACE_OFF_ROADVEH = SPR_GROUP_BASE + 17,
+ SPR_GROUP_REPLACE_OFF_SHIP = SPR_GROUP_BASE + 18,
+ SPR_GROUP_REPLACE_OFF_AIRCRAFT = SPR_GROUP_BASE + 19,
+
/* Manager face sprites */
SPR_GRADIENT = 874, // background gradient behind manager face
diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp
index 9d4bb36f1..3329f8d0a 100644
--- a/src/train_cmd.cpp
+++ b/src/train_cmd.cpp
@@ -37,6 +37,7 @@
#include "yapf/yapf.h"
#include "date.h"
#include "cargotype.h"
+#include "group.h"
static bool TrainCheckIfLineEnds(Vehicle *v);
static void TrainController(Vehicle *v, bool update_image);
@@ -637,12 +638,15 @@ static int32 CmdBuildRailWagon(EngineID engine, TileIndex tile, uint32 flags)
v->cur_image = 0xAC2;
v->random_bits = VehicleRandomBits();
+ v->group_id = DEFAULT_GROUP;
+
AddArticulatedParts(vl);
_new_vehicle_id = v->index;
VehiclePositionChanged(v);
TrainConsistChanged(GetFirstVehicleInChain(v));
+ UpdateTrainGroupID(GetFirstVehicleInChain(v));
InvalidateWindow(WC_VEHICLE_DEPOT, v->tile);
if (IsLocalPlayer()) {
@@ -797,6 +801,8 @@ int32 CmdBuildRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
v->vehicle_flags = 0;
if (e->flags & ENGINE_EXCLUSIVE_PREVIEW) SETBIT(v->vehicle_flags, VF_BUILT_AS_PROTOTYPE);
+ v->group_id = DEFAULT_GROUP;
+
v->subtype = 0;
SetFrontEngine(v);
SetTrainEngine(v);
@@ -818,6 +824,7 @@ int32 CmdBuildRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
TrainConsistChanged(v);
UpdateTrainAcceleration(v);
+ UpdateTrainGroupID(v);
if (!HASBIT(p2, 1)) { // check if the cars should be added to the new vehicle
NormalizeTrainVehInDepot(v);
@@ -1113,6 +1120,16 @@ int32 CmdMoveRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
for (Vehicle *u = src_head; u != NULL; u = u->next) u->first = NULL;
for (Vehicle *u = dst_head; u != NULL; u = u->next) u->first = NULL;
+ /* If we move the front Engine and if the second vehicle is not an engine
+ add the whole vehicle to the DEFAULT_GROUP */
+ if (IsFrontEngine(src) && !IsDefaultGroupID(src->group_id)) {
+ const Vehicle *v = GetNextVehicle(src);
+
+ if (v != NULL && !IsTrainEngine(v)) {
+ DoCommand(tile, DEFAULT_GROUP, v->index, flags, CMD_ADD_VEHICLE_GROUP);
+ }
+ }
+
if (HASBIT(p2, 0)) {
/* unlink ALL wagons */
if (src != src_head) {
@@ -1142,6 +1159,14 @@ int32 CmdMoveRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
SetFrontEngine(src);
assert(src->orders == NULL);
src->num_orders = 0;
+
+ // Decrease the engines number of the src engine_type
+ if (!IsDefaultGroupID(src->group_id) && IsValidGroupID(src->group_id)) {
+ GetGroup(src->group_id)->num_engines[src->engine_type]--;
+ }
+
+ // If we move an engine to a new line affect it to the DEFAULT_GROUP
+ src->group_id = DEFAULT_GROUP;
}
} else {
SetFreeWagon(src);
@@ -1203,13 +1228,18 @@ int32 CmdMoveRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
* To do this, CmdMoveRailVehicle must be called once more
* we can't loop forever here because next time we reach this line we will have a front engine */
if (src_head != NULL && !IsFrontEngine(src_head) && IsTrainEngine(src_head)) {
+ /* As in CmdMoveRailVehicle src_head->group_id will be equal to DEFAULT_GROUP
+ * we need to save the group and reaffect it to src_head */
+ const GroupID tmp_g = src_head->group_id;
CmdMoveRailVehicle(0, flags, src_head->index | (INVALID_VEHICLE << 16), 1);
+ SetTrainGroupID(src_head, tmp_g);
src_head = NULL; // don't do anything more to this train since the new call will do it
}
if (src_head != NULL) {
NormaliseTrainConsist(src_head);
TrainConsistChanged(src_head);
+ UpdateTrainGroupID(src_head);
if (IsFrontEngine(src_head)) {
UpdateTrainAcceleration(src_head);
InvalidateWindow(WC_VEHICLE_DETAILS, src_head->index);
@@ -1224,6 +1254,7 @@ int32 CmdMoveRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
if (dst_head != NULL) {
NormaliseTrainConsist(dst_head);
TrainConsistChanged(dst_head);
+ UpdateTrainGroupID(dst_head);
if (IsFrontEngine(dst_head)) {
UpdateTrainAcceleration(dst_head);
InvalidateWindow(WC_VEHICLE_DETAILS, dst_head->index);
@@ -1364,6 +1395,8 @@ int32 CmdSellRailWagon(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
if (first->next_shared != NULL) {
first->next_shared->prev_shared = new_f;
new_f->next_shared = first->next_shared;
+ } else {
+ RemoveVehicleFromGroup(v);
}
/*
@@ -1394,6 +1427,7 @@ int32 CmdSellRailWagon(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
if (first != NULL) {
NormaliseTrainConsist(first);
TrainConsistChanged(first);
+ UpdateTrainGroupID(first);
if (IsFrontEngine(first)) {
InvalidateWindow(WC_VEHICLE_DETAILS, first->index);
InvalidateWindow(WC_VEHICLE_REFIT, first->index);
@@ -1447,6 +1481,7 @@ int32 CmdSellRailWagon(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
first = UnlinkWagon(v, first);
DeleteDepotHighlightOfVehicle(v);
DeleteVehicle(v);
+ RemoveVehicleFromGroup(v);
}
}
@@ -1454,6 +1489,7 @@ int32 CmdSellRailWagon(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
if (flags & DC_EXEC && first != NULL) {
NormaliseTrainConsist(first);
TrainConsistChanged(first);
+ UpdateTrainGroupID(first);
if (IsFrontEngine(first)) UpdateTrainAcceleration(first);
InvalidateWindow(WC_VEHICLE_DETAILS, first->index);
InvalidateWindow(WC_VEHICLE_REFIT, first->index);
@@ -3063,6 +3099,9 @@ static void DeleteLastWagon(Vehicle *v)
BeginVehicleMove(v);
EndVehicleMove(v);
+
+ if (IsFrontEngine(v)) RemoveVehicleFromGroup(v);
+
DeleteVehicle(v);
if (v->u.rail.track != TRACK_BIT_DEPOT && v->u.rail.track != TRACK_BIT_WORMHOLE)
@@ -3148,6 +3187,7 @@ static void HandleCrashedTrain(Vehicle *v)
if (state >= 4440 && !(v->tick_counter&0x1F)) {
DeleteLastWagon(v);
+ InvalidateWindow(WC_REPLACE_VEHICLE, (v->group_id << 16) | VEH_TRAIN);
}
}
diff --git a/src/variables.h b/src/variables.h
index bb371d95d..6b79367d1 100644
--- a/src/variables.h
+++ b/src/variables.h
@@ -130,6 +130,7 @@ struct Patches {
bool measure_tooltip; // Show a permanent tooltip when dragging tools
byte liveries; // Options for displaying company liveries, 0=none, 1=self, 2=all
bool prefer_teamchat; // Choose the chat message target with <ENTER>, true=all players, false=your team
+ bool advanced_vehicle_list; // Use the "advanced" vehicle list
uint8 toolbar_pos; // position of toolbars, 0=left, 1=center, 2=right
uint8 window_snap_radius; // Windows snap at each other if closer than this
diff --git a/src/vehicle.cpp b/src/vehicle.cpp
index 22fd5d8fe..c102108ae 100644
--- a/src/vehicle.cpp
+++ b/src/vehicle.cpp
@@ -40,6 +40,7 @@
#include "newgrf_engine.h"
#include "newgrf_sound.h"
#include "helpers.hpp"
+#include "group.h"
#include "economy.h"
#define INVALID_COORD (-0x8000)
@@ -111,7 +112,7 @@ bool VehicleNeedsService(const Vehicle *v)
return false; // Crashed vehicles don't need service anymore
if (_patches.no_servicing_if_no_breakdowns && _opt.diff.vehicle_breakdowns == 0) {
- return EngineHasReplacementForPlayer(GetPlayer(v->owner), v->engine_type); /* Vehicles set for autoreplacing needs to go to a depot even if breakdowns are turned off */
+ return EngineHasReplacementForPlayer(GetPlayer(v->owner), v->engine_type, v->group_id); /* Vehicles set for autoreplacing needs to go to a depot even if breakdowns are turned off */
}
return _patches.servint_ispercent ?
@@ -284,6 +285,8 @@ static Vehicle *InitializeVehicle(Vehicle *v)
v->prev_shared = NULL;
v->depot_list = NULL;
v->random_bits = 0;
+ v->group_id = DEFAULT_GROUP;
+
return v;
}
@@ -580,6 +583,8 @@ void DestroyVehicle(Vehicle *v)
if (IsEngineCountable(v)) {
GetPlayer(v->owner)->num_engines[v->engine_type]--;
if (v->owner == _local_player) InvalidateAutoreplaceWindow(v->engine_type);
+
+ if (!IsDefaultGroupID(v->group_id) && IsValidGroupID(v->group_id)) GetGroup(v->group_id)->num_engines[v->engine_type]--;
}
DeleteVehicleNews(v->index, INVALID_STRING_ID);
@@ -1821,6 +1826,12 @@ int32 CmdCloneVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
_new_vehicle_id = w_front->index;
}
+ if (flags & DC_EXEC) {
+ /* Cloned vehicles belong to the same group */
+ DoCommand(0, v_front->group_id, w_front->index, flags, CMD_ADD_VEHICLE_GROUP);
+ }
+
+
/* Take care of refitting. */
w = w_front;
v = v_front;
@@ -1973,6 +1984,7 @@ void BuildDepotVehicleList(VehicleType type, TileIndex tile, Vehicle ***engine_l
<li>VLW_SHARED_ORDERS: index of order to generate a list for<li>
<li>VLW_STANDARD: not used<li>
<li>VLW_DEPOT_LIST: TileIndex of the depot/hangar to make the list for</li>
+ <li>VLW_GROUP_LIST: index of group to generate a list for</li>
</ul>
* @param window_type tells what kind of window the list is for. Use the VLW flags in vehicle_gui.h
* @return the number of vehicles added to the list
@@ -2051,6 +2063,19 @@ uint GenerateVehicleSortList(const Vehicle ***sort_list, uint16 *length_of_array
break;
}
+ case VLW_GROUP_LIST:
+ FOR_ALL_VEHICLES(v) {
+ if (v->type == type && (
+ (type == VEH_TRAIN && IsFrontEngine(v)) ||
+ (type != VEH_TRAIN && v->subtype <= subtype)
+ ) && v->owner == owner && v->group_id == index) {
+ if (n == *length_of_array) ExtendVehicleListSize(sort_list, length_of_array, GetNumVehicles() / 4);
+
+ (*sort_list)[n++] = v;
+ }
+ }
+ break;
+
default: NOT_REACHED(); break;
}
@@ -2667,6 +2692,8 @@ extern const SaveLoad _common_veh_desc[] = {
SLE_REF(Vehicle, next_shared, REF_VEHICLE),
SLE_REF(Vehicle, prev_shared, REF_VEHICLE),
+ SLE_CONDVAR(Vehicle, group_id, SLE_UINT16, 60, SL_MAX_VERSION),
+
/* reserve extra space in savegame here. (currently 10 bytes) */
SLE_CONDNULL(10, 2, SL_MAX_VERSION),
@@ -2865,6 +2892,9 @@ static void Load_VEHS()
v->current_order.flags = (v->current_order.type & 0xF0) >> 4;
v->current_order.type.m_val &= 0x0F;
}
+
+ /* Advanced vehicle lists got added */
+ if (CheckSavegameVersion(60)) v->group_id = DEFAULT_GROUP;
}
/* Check for shared order-lists (we now use pointers for that) */
diff --git a/src/vehicle.h b/src/vehicle.h
index d2f66c1be..5d2eaf7c2 100644
--- a/src/vehicle.h
+++ b/src/vehicle.h
@@ -310,6 +310,8 @@ struct Vehicle {
TileIndex cargo_loaded_at_xy; ///< tile index where feeder cargo was loaded
uint32 value;
+ GroupID group_id; ///< Index of group Pool array
+
union {
VehicleRail rail;
VehicleAir air;
diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp
index f9859e917..03e232205 100644
--- a/src/vehicle_gui.cpp
+++ b/src/vehicle_gui.cpp
@@ -31,6 +31,7 @@
#include "depot.h"
#include "helpers.hpp"
#include "cargotype.h"
+#include "group.h"
struct Sorting {
Listing aircraft;
@@ -41,15 +42,6 @@ struct Sorting {
static Sorting _sorting;
-struct vehiclelist_d {
- const Vehicle** sort_list; // List of vehicles (sorted)
- Listing *_sorting; // pointer to the appropiate subcategory of _sorting
- uint16 length_of_sort_list; // Keeps track of how many vehicle pointers sort list got space for
- VehicleType vehicle_type; // The vehicle type that is sorted
- list_d l; // General list struct
-};
-assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(vehiclelist_d));
-
static bool _internal_sort_order; // descending/ascending
typedef int CDECL VehicleSortListingTypeFunction(const void*, const void*);
@@ -78,7 +70,7 @@ static VehicleSortListingTypeFunction* const _vehicle_sorter[] = {
&VehicleValueSorter,
};
-static const StringID _vehicle_sort_listing[] = {
+const StringID _vehicle_sort_listing[] = {
STR_SORT_BY_NUMBER,
STR_SORT_BY_DROPDOWN_NAME,
STR_SORT_BY_AGE,
@@ -134,7 +126,7 @@ void ResortVehicleLists()
}
}
-static void BuildVehicleList(vehiclelist_d* vl, PlayerID owner, uint16 index, uint16 window_type)
+void BuildVehicleList(vehiclelist_d *vl, PlayerID owner, uint16 index, uint16 window_type)
{
if (!(vl->l.flags & VL_REBUILD)) return;
@@ -146,7 +138,7 @@ static void BuildVehicleList(vehiclelist_d* vl, PlayerID owner, uint16 index, ui
vl->l.flags |= VL_RESORT;
}
-static void SortVehicleList(vehiclelist_d *vl)
+void SortVehicleList(vehiclelist_d *vl)
{
if (!(vl->l.flags & VL_RESORT)) return;
@@ -749,16 +741,6 @@ void ChangeVehicleViewWindow(const Vehicle *from_v, const Vehicle *to_v)
}
}
-/*
- * Start of functions regarding vehicle list windows
- */
-
-enum {
- PLY_WND_PRC__OFFSET_TOP_WIDGET = 26,
- PLY_WND_PRC__SIZE_OF_ROW_SMALL = 26,
- PLY_WND_PRC__SIZE_OF_ROW_BIG = 36,
-};
-
enum VehicleListWindowWidgets {
VLW_WIDGET_CLOSEBOX = 0,
VLW_WIDGET_CAPTION,
@@ -926,7 +908,7 @@ static void CreateVehicleListWindow(Window *w)
vl->l.resort_timer = DAY_TICKS * PERIODIC_RESORT_DAYS; // Set up resort timer
}
-static void DrawSmallOrderList(const Vehicle *v, int x, int y)
+void DrawSmallOrderList(const Vehicle *v, int x, int y)
{
const Order *order;
int sel, i = 0;
@@ -1275,7 +1257,11 @@ static void ShowVehicleListWindowLocal(PlayerID player, uint16 VLW_flag, Vehicle
void ShowVehicleListWindow(PlayerID player, VehicleType vehicle_type)
{
- ShowVehicleListWindowLocal(player, VLW_STANDARD, vehicle_type, 0);
+ if (player == _local_player && _patches.advanced_vehicle_list) {
+ ShowPlayerGroup(player, vehicle_type);
+ } else {
+ ShowVehicleListWindowLocal(player, VLW_STANDARD, vehicle_type, 0);
+ }
}
void ShowVehicleListWindow(const Vehicle *v)
diff --git a/src/vehicle_gui.h b/src/vehicle_gui.h
index 2e54de24c..1cf61073c 100644
--- a/src/vehicle_gui.h
+++ b/src/vehicle_gui.h
@@ -15,21 +15,35 @@ void InitializeVehiclesGuiList();
/* sorter stuff */
void RebuildVehicleLists();
void ResortVehicleLists();
+void SortVehicleList(vehiclelist_d *vl);
+void BuildVehicleList(vehiclelist_d *vl, PlayerID owner, uint16 index, uint16 window_type);
#define PERIODIC_RESORT_DAYS 10
+extern const StringID _vehicle_sort_listing[];
+
+/* Start of functions regarding vehicle list windows */
+enum {
+ PLY_WND_PRC__OFFSET_TOP_WIDGET = 26,
+ PLY_WND_PRC__SIZE_OF_ROW_TINY = 13,
+ PLY_WND_PRC__SIZE_OF_ROW_SMALL = 26,
+ PLY_WND_PRC__SIZE_OF_ROW_BIG = 36,
+ PLY_WND_PRC__SIZE_OF_ROW_BIG2 = 39,
+};
+
/* Vehicle List Window type flags */
enum {
VLW_STANDARD = 0 << 8,
VLW_SHARED_ORDERS = 1 << 8,
VLW_STATION_LIST = 2 << 8,
VLW_DEPOT_LIST = 3 << 8,
+ VLW_GROUP_LIST = 4 << 8,
VLW_MASK = 0x700,
};
static inline bool ValidVLWFlags(uint16 flags)
{
- return (flags == VLW_STANDARD || flags == VLW_SHARED_ORDERS || flags == VLW_STATION_LIST || flags == VLW_DEPOT_LIST);
+ return (flags == VLW_STANDARD || flags == VLW_SHARED_ORDERS || flags == VLW_STATION_LIST || flags == VLW_DEPOT_LIST || flags == VLW_GROUP_LIST);
}
void PlayerVehWndProc(Window *w, WindowEvent *e);
@@ -54,6 +68,8 @@ void ShowVehicleListWindow(PlayerID player, VehicleType vehicle_type, StationID
void ShowVehicleListWindow(PlayerID player, VehicleType vehicle_type, TileIndex depot_tile);
void ShowReplaceVehicleWindow(VehicleType vehicletype);
+void DrawSmallOrderList(const Vehicle *v, int x, int y);
+void ShowReplaceGroupVehicleWindow(GroupID group, VehicleType veh);
static inline void DrawVehicleImage(const Vehicle *v, int x, int y, int count, int skip, VehicleID selection)
{
diff --git a/src/window.h b/src/window.h
index 85bb74dac..6cc255178 100644
--- a/src/window.h
+++ b/src/window.h
@@ -348,6 +348,7 @@ struct replaceveh_d {
bool update_left;
bool update_right;
bool init_lists;
+ GroupID sel_group;
};
assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(replaceveh_d));
@@ -478,6 +479,28 @@ struct dropdown_d {
};
assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(dropdown_d));
+struct vehiclelist_d {
+ const Vehicle** sort_list; // List of vehicles (sorted)
+ Listing *_sorting; // pointer to the appropiate subcategory of _sorting
+ uint16 length_of_sort_list; // Keeps track of how many vehicle pointers sort list got space for
+ VehicleType vehicle_type; // The vehicle type that is sorted
+ list_d l; // General list struct
+};
+assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(vehiclelist_d));
+
+struct grouplist_d {
+ const Group **sort_list;
+ list_d l; // General list struct
+};
+assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(grouplist_d));
+
+struct groupveh_d : vehiclelist_d {
+ GroupID group_sel;
+ VehicleID vehicle_sel;
+
+ grouplist_d gl;
+};
+assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(groupveh_d));
/****************** THESE ARE NOT WIDGET TYPES!!!!! *******************/
enum WindowWidgetBehaviours {