diff --git a/bin/data/group.grf b/bin/data/group.grf new file mode 100644 index 0000000000..586a2bfd16 Binary files /dev/null and b/bin/data/group.grf differ diff --git a/projects/openttd.vcproj b/projects/openttd.vcproj index 0cbc3ce823..9bfe7b14e4 100644 --- a/projects/openttd.vcproj +++ b/projects/openttd.vcproj @@ -452,6 +452,9 @@ + + @@ -702,6 +705,9 @@ + + @@ -790,6 +796,9 @@ + + diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj index c538253ed3..87f6b0784c 100644 --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -831,6 +831,10 @@ RelativePath=".\..\src\gfxinit.h" > + + @@ -1163,6 +1167,10 @@ RelativePath=".\..\src\graph_gui.cpp" > + + @@ -1279,6 +1287,10 @@ RelativePath=".\..\src\dummy_land.cpp" > + + diff --git a/source.list b/source.list index 5a18eddcf3..740c43b99f 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 0d4c3ba109..90192e0230 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 59e2ac44df..2774f22341 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 03c1610e8a..9a0e1467e1 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 33a08f40cd..746d8bbc96 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 49029ee7ec..3fa2bcfec4 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 7a61196522..4cf9844e39 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 d6f0e629c7..9895001475 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 e4d1e6e8dc..b0336cc2d9 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 c54e3bb109..b34452c076 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 01c1ded80c..6ff742f0c5 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 0000000000..f68306293d --- /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 0000000000..b44e92fbe8 --- /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 0000000000..8a486d644b --- /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(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(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 71be707a61..d2f3be76f2 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 b10e857b1a..d9ada440ba 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 9ea8a738ea..2dd2750d00 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 a2bb58b434..b4353612b5 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 f4511ea921..0c66d9275f 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 297bd49706..0732bf8682 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 1a7487ce9c..b142f669b5 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 a4963e7452..6550fdfdd1 100644 --- a/src/saveload.cpp +++ b/src/saveload.cpp @@ -29,7 +29,7 @@ #include #include -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 203d5e0ffb..f71bc862f1 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 f5d846b415..140337f766 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 e480f61637..221bd03579 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 b0b6fb1a77..eae48281ad 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 a1ff42a899..6daaee0eb9 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 3a2cb6d1ea..79522ab34c 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 a1a12e5ad6..83d8e93775 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 9d4bb36f1c..3329f8d0a2 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 bb371d95d5..6b79367d1a 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 , 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 22fd5d8fee..c102108aee 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
  • VLW_SHARED_ORDERS: index of order to generate a list for
  • VLW_STANDARD: not used
  • VLW_DEPOT_LIST: TileIndex of the depot/hangar to make the list for
  • +
  • VLW_GROUP_LIST: index of group to generate a list for
  • * @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 d2f66c1be6..5d2eaf7c22 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 f9859e9170..03e2322058 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 2e54de24c4..1cf61073cb 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 85bb74dacf..6cc2551782 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 {