diff options
author | rubidium <rubidium@openttd.org> | 2007-05-19 09:40:18 +0000 |
---|---|---|
committer | rubidium <rubidium@openttd.org> | 2007-05-19 09:40:18 +0000 |
commit | 8f0f090c5139465bb2db1bf280886a563a68385d (patch) | |
tree | fcea59953bd1ce2ff5f0302669d3649bf7ca78bb /src | |
parent | 9a4b4ba4484112c7eefa6ed134ec4d725be7d2a7 (diff) | |
download | openttd-8f0f090c5139465bb2db1bf280886a563a68385d.tar.xz |
(svn r9874) -Feature: advanced vehicle lists a.k.a. group interface. Now you can make groups of vehicles and perform all kinds of tasks on that given group. Original code by nycom and graphics by skidd13.
Diffstat (limited to 'src')
35 files changed, 1654 insertions, 67 deletions
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; +} @@ -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 { |