(svn r3155) -Feature: [autoreplace] autoreplace can now remove cars from too long trains

-Trains will now remember the length of stations it visits and sell cars
        when being autoreplaced if they became too long
        -If it needs to remove cars, then it starts from the front and sells
        all it can find until the train is short enough
        -This only works for trains, that knows the station length of the route
        so a full uninterrupted run is needed
        -a train needs 1-2 runs to detect if the shortest station is expanded
        -This feature can be turned on and off in the train replace window
        and each company can have it's own setting
        -NOTE: minor savegame version bump
This commit is contained in:
bjarni 2005-11-07 23:20:47 +00:00
parent 723e789b2f
commit 5481dcd191
12 changed files with 129 additions and 14 deletions

View File

@ -943,6 +943,7 @@ STR_TRAIN_AUTORENEW_FAILED :{WHITE}Autorene
STR_ROADVEHICLE_AUTORENEW_FAILED :{WHITE}Autorenew failed on road vehicle {COMMA} (money limit) STR_ROADVEHICLE_AUTORENEW_FAILED :{WHITE}Autorenew failed on road vehicle {COMMA} (money limit)
STR_SHIP_AUTORENEW_FAILED :{WHITE}Autorenew failed on ship {COMMA} (money limit) STR_SHIP_AUTORENEW_FAILED :{WHITE}Autorenew failed on ship {COMMA} (money limit)
STR_AIRCRAFT_AUTORENEW_FAILED :{WHITE}Autorenew failed on aircraft {COMMA} (money limit) STR_AIRCRAFT_AUTORENEW_FAILED :{WHITE}Autorenew failed on aircraft {COMMA} (money limit)
STR_TRAIN_TOO_LONG_AFTER_REPLACEMENT :{WHITE}Train {COMMA} is too long after replacement
STR_CONFIG_PATCHES :{BLACK}Configure Patches STR_CONFIG_PATCHES :{BLACK}Configure Patches
STR_CONFIG_PATCHES_TIP :{BLACK}Configure the patches STR_CONFIG_PATCHES_TIP :{BLACK}Configure the patches
@ -2752,6 +2753,8 @@ STR_REPLACE_HELP_START_BUTTON :{BLACK}Press to
STR_REPLACE_HELP_RAILTYPE :{BLACK}Choose the railtype you want to replace engines for STR_REPLACE_HELP_RAILTYPE :{BLACK}Choose the railtype you want to replace engines for
STR_REPLACE_HELP_REPLACE_INFO_TAB :{BLACK}Displays which engine the left selected engine is being replaced with, if any STR_REPLACE_HELP_REPLACE_INFO_TAB :{BLACK}Displays which engine the left selected engine is being replaced with, if any
STR_REPLACE_HELP :{BLACK}This allows you to replace one engine type with another type, when trains of the original type enter a depot STR_REPLACE_HELP :{BLACK}This allows you to replace one engine type with another type, when trains of the original type enter a depot
STR_REPLACE_REMOVE_WAGON :{BLACK}Wagon removal: {ORANGE}{SKIP}{STRING}
STR_REPLACE_REMOVE_WAGON_HELP :{BLACK}Setting this to "On" will make autoreplace remove wagons from trains to make them keep their length if they exceed length of the shortest station in their orders.{}It will remove as many wagons as needed starting from the front
STR_SHORT_DATE :{WHITE}{DATE_TINY} STR_SHORT_DATE :{WHITE}{DATE_TINY}
STR_SIGN_LIST_CAPTION :{WHITE}Sign List - {COMMA} Sign{P "" s} STR_SIGN_LIST_CAPTION :{WHITE}Sign List - {COMMA} Sign{P "" s}

View File

@ -1300,6 +1300,23 @@ bool AfterLoadGame(uint version)
} }
} }
/* In version 16.1 of the savegame, trains became aware of station lengths
need to initialized to the invalid state
players needs to set renew_keep_length too */
if (version < 0x1001) {
Vehicle *v;
FOR_ALL_PLAYERS(p) {
p->renew_keep_length = false;
}
FOR_ALL_VEHICLES(v) {
if (v->type == VEH_Train) {
v->u.rail.shortest_platform[0] = 255;
v->u.rail.shortest_platform[1] = 0;
}
}
}
FOR_ALL_PLAYERS(p) { FOR_ALL_PLAYERS(p) {
p->avail_railtypes = GetPlayerRailtypes(p->index); p->avail_railtypes = GetPlayerRailtypes(p->index);
} }

View File

@ -388,6 +388,7 @@ int32 CmdInsertOrder(int x, int y, uint32 flags, uint32 p1, uint32 p2)
} }
/* Update any possible open window of the vehicle */ /* Update any possible open window of the vehicle */
InvalidateVehicleOrder(u); InvalidateVehicleOrder(u);
if (u->type == VEH_Train) u->u.rail.shortest_platform[1] = 0; // we changed the orders so we invalidate the station length collector
u = u->next_shared; u = u->next_shared;
} }
@ -520,6 +521,7 @@ int32 CmdSkipOrder(int x, int y, uint32 flags, uint32 p1, uint32 p2)
if (v->current_order.type == OT_LOADING && HASBIT(v->current_order.flags, OFB_NON_STOP)) if (v->current_order.type == OT_LOADING && HASBIT(v->current_order.flags, OFB_NON_STOP))
v->current_order.flags = 0; v->current_order.flags = 0;
if (v->type == VEH_Train) v->u.rail.shortest_platform[1] = 0; // we changed the orders so we invalidate the station length collector
InvalidateVehicleOrder(v); InvalidateVehicleOrder(v);
} }
@ -663,6 +665,7 @@ int32 CmdCloneOrder(int x, int y, uint32 flags, uint32 p1, uint32 p2)
InvalidateVehicleOrder(src); InvalidateVehicleOrder(src);
RebuildVehicleLists(); RebuildVehicleLists();
if (dst->type == VEH_Train) dst->u.rail.shortest_platform[1] = 0; // we changed the orders so we invalidate the station length collector
} }
} break; } break;
@ -722,6 +725,7 @@ int32 CmdCloneOrder(int x, int y, uint32 flags, uint32 p1, uint32 p2)
InvalidateVehicleOrder(dst); InvalidateVehicleOrder(dst);
RebuildVehicleLists(); RebuildVehicleLists();
if (dst->type == VEH_Train) dst->u.rail.shortest_platform[1] = 0; // we changed the orders so we invalidate the station length collector
} }
} break; } break;

View File

@ -190,6 +190,7 @@ typedef struct Player {
PlayerEconomyEntry old_economy[24]; PlayerEconomyEntry old_economy[24];
EngineID engine_replacement[TOTAL_NUM_ENGINES]; EngineID engine_replacement[TOTAL_NUM_ENGINES];
bool engine_renew; bool engine_renew;
bool renew_keep_length;
int16 engine_renew_months; int16 engine_renew_months;
uint32 engine_renew_money; uint32 engine_renew_money;
} Player; } Player;

View File

@ -497,6 +497,7 @@ Player *DoStartupNewPlayer(bool is_ai)
for (i = 0; i < TOTAL_NUM_ENGINES; i++) for (i = 0; i < TOTAL_NUM_ENGINES; i++)
p->engine_replacement[i] = INVALID_ENGINE; p->engine_replacement[i] = INVALID_ENGINE;
p->renew_keep_length = false;
p->engine_renew = false; p->engine_renew = false;
p->engine_renew_months = -6; p->engine_renew_months = -6;
p->engine_renew_money = 100000; p->engine_renew_money = 100000;
@ -654,6 +655,7 @@ static void DeletePlayerStuff(PlayerID pi)
* - p1 = 2 - change auto renew money * - p1 = 2 - change auto renew money
* - p1 = 3 - change auto renew array * - p1 = 3 - change auto renew array
* - p1 = 4 - change bool, months & money all together * - p1 = 4 - change bool, months & money all together
* - p1 = 5 - change renew_keep_length
* @param p2 value to set * @param p2 value to set
* if p1 = 0, then: * if p1 = 0, then:
* - p2 = enable engine renewal * - p2 = enable engine renewal
@ -668,6 +670,8 @@ static void DeletePlayerStuff(PlayerID pi)
* - p1 bit 15 = enable engine renewal * - p1 bit 15 = enable engine renewal
* - p1 bits 16-31 = months left before engine expires to replace it * - p1 bits 16-31 = months left before engine expires to replace it
* - p2 bits 0-31 = minimum amount of money available * - p2 bits 0-31 = minimum amount of money available
* if p1 = 5, then
* - p2 = enable renew_keep_length
*/ */
int32 CmdReplaceVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) int32 CmdReplaceVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{ {
@ -754,8 +758,19 @@ int32 CmdReplaceVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2)
} }
} }
break; break;
} case 5:
if (p->renew_keep_length == (bool)GB(p2, 0, 1))
return CMD_ERROR;
if (flags & DC_EXEC) {
p->renew_keep_length = (bool)GB(p2, 0, 1);
if (IsLocalPlayer()) {
InvalidateWindow(WC_REPLACE_VEHICLE, VEH_Train);
}
}
break;
}
return 0; return 0;
} }
@ -1131,9 +1146,11 @@ static const SaveLoad _player_desc[] = {
SLE_CONDVAR(Player,engine_renew, SLE_UINT8, 16, 255), SLE_CONDVAR(Player,engine_renew, SLE_UINT8, 16, 255),
SLE_CONDVAR(Player,engine_renew_months, SLE_INT16, 16, 255), SLE_CONDVAR(Player,engine_renew_months, SLE_INT16, 16, 255),
SLE_CONDVAR(Player,engine_renew_money, SLE_UINT32, 16, 255), SLE_CONDVAR(Player,engine_renew_money, SLE_UINT32, 16, 255),
SLE_CONDVAR(Player,renew_keep_length, SLE_UINT8, 2, 255), // added with 16.1, but was blank since 2
// reserve extra space in savegame here. (currently 64 bytes) // reserve extra space in savegame here. (currently 63 bytes)
SLE_CONDARR(NullStruct,null,SLE_FILE_U64 | SLE_VAR_NULL, 8, 2, 255), SLE_CONDARR(NullStruct,null,SLE_FILE_U8 | SLE_VAR_NULL, 7, 2, 255),
SLE_CONDARR(NullStruct,null,SLE_FILE_U64 | SLE_VAR_NULL, 7, 2, 255),
SLE_END() SLE_END()
}; };

View File

@ -30,7 +30,7 @@
enum { enum {
SAVEGAME_MAJOR_VERSION = 16, SAVEGAME_MAJOR_VERSION = 16,
SAVEGAME_MINOR_VERSION = 0, SAVEGAME_MINOR_VERSION = 1,
SAVEGAME_LOADABLE_VERSION = (SAVEGAME_MAJOR_VERSION << 8) + SAVEGAME_MINOR_VERSION SAVEGAME_LOADABLE_VERSION = (SAVEGAME_MAJOR_VERSION << 8) + SAVEGAME_MINOR_VERSION
}; };

View File

@ -320,6 +320,11 @@ static inline bool IsBuoyTile(TileIndex tile)
return IsTileType(tile, MP_STATION) && _m[tile].m5 == 0x52; return IsTileType(tile, MP_STATION) && _m[tile].m5 == 0x52;
} }
static inline bool TileBelongsToRailStation(const Station *st, TileIndex tile)
{
return IsTileType(tile, MP_STATION) && _m[tile].m2 == st->index && _m[tile].m5 < 8;
}
/* Get's the direction the station exit points towards. Ie, returns 0 for a /* Get's the direction the station exit points towards. Ie, returns 0 for a
* station with the exit NE. */ * station with the exit NE. */
static inline byte GetRoadStationDir(TileIndex tile) static inline byte GetRoadStationDir(TileIndex tile)

View File

@ -1083,11 +1083,6 @@ int32 CmdBuildRailroadStation(int x, int y, uint32 flags, uint32 p1, uint32 p2)
return cost; return cost;
} }
static bool TileBelongsToRailStation(const Station *st, TileIndex tile)
{
return IsTileType(tile, MP_STATION) && _m[tile].m2 == st->index && _m[tile].m5 < 8;
}
static void MakeRailwayStationAreaSmaller(Station *st) static void MakeRailwayStationAreaSmaller(Station *st)
{ {
uint w = st->trainst_w; uint w = st->trainst_w;

View File

@ -752,6 +752,9 @@ int32 CmdBuildRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2)
v->type = VEH_Train; v->type = VEH_Train;
v->cur_image = 0xAC2; v->cur_image = 0xAC2;
v->u.rail.shortest_platform[0] = 255;
v->u.rail.shortest_platform[1] = 0;
VehiclePositionChanged(v); VehiclePositionChanged(v);
if (rvi->flags & RVI_MULTIHEAD && !HASBIT(p2, 0)) { if (rvi->flags & RVI_MULTIHEAD && !HASBIT(p2, 0)) {
@ -2349,6 +2352,27 @@ static bool ProcessTrainOrder(Vehicle *v)
v->dest_tile = 0; v->dest_tile = 0;
// store the station length if no shorter station was visited this order round
if (v->cur_order_index == 0) {
if (v->u.rail.shortest_platform[1] != 0 && v->u.rail.shortest_platform[1] != 255) {
// we went though a whole round of orders without interruptions, so we store the length of the shortest station
v->u.rail.shortest_platform[0] = v->u.rail.shortest_platform[1];
}
// all platforms are shorter than 255, so now we can find the shortest in the next order round. They might have changed size
v->u.rail.shortest_platform[1] = 255;
}
if (v->last_station_visited != INVALID_STATION) {
Station *st = GetStation(v->last_station_visited);
if (TileBelongsToRailStation(st, v->tile)) {
byte length = GetStationPlatforms(st, v->tile);
if (length < v->u.rail.shortest_platform[1]) {
v->u.rail.shortest_platform[1] = length;
}
}
}
result = false; result = false;
switch (order->type) { switch (order->type) {
case OT_GOTO_STATION: case OT_GOTO_STATION:

View File

@ -1693,6 +1693,8 @@ static int32 ReplaceVehicle(Vehicle **w, byte flags)
if (old_v->type == VEH_Train){ if (old_v->type == VEH_Train){
// move the entire train to the new engine, including the old engine. It will be sold in a moment anyway // move the entire train to the new engine, including the old engine. It will be sold in a moment anyway
DoCommand(0, 0, (new_v->index << 16) | old_v->index, 1, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); DoCommand(0, 0, (new_v->index << 16) | old_v->index, 1, DC_EXEC, CMD_MOVE_RAIL_VEHICLE);
new_v->u.rail.shortest_platform[0] = old_v->u.rail.shortest_platform[0];
new_v->u.rail.shortest_platform[1] = old_v->u.rail.shortest_platform[1];
} }
} }
} }
@ -1720,6 +1722,7 @@ static void MaybeReplaceVehicle(Vehicle *v)
byte flags = 0; byte flags = 0;
int32 cost, temp_cost = 0; int32 cost, temp_cost = 0;
bool stopped = false; bool stopped = false;
bool train_fits_in_station = false;
_current_player = v->owner; _current_player = v->owner;
@ -1733,6 +1736,11 @@ static void MaybeReplaceVehicle(Vehicle *v)
stopped = true; stopped = true;
} }
if (v->type == VEH_Train && v->u.rail.shortest_platform[0]*16 <= v->u.rail.cached_total_length && GetPlayer(v->owner)->renew_keep_length) {
// the train is not too long for the stations it visits. We should try to keep it that way if we change anything
train_fits_in_station = true;
}
while (true) { while (true) {
cost = 0; cost = 0;
w = v; w = v;
@ -1794,6 +1802,27 @@ static void MaybeReplaceVehicle(Vehicle *v)
flags |= DC_EXEC; flags |= DC_EXEC;
} }
if (train_fits_in_station) {
// the train fitted in the stations it got in it's orders, so we should make sure that it still do
Vehicle *temp;
w = v;
while (v->u.rail.shortest_platform[0]*16 < v->u.rail.cached_total_length) {
// the train is too long. We will remove cars one by one from the start of the train until it's short enough
while (w != NULL && !(RailVehInfo(w->engine_type)->flags&RVI_WAGON) ) {
w = GetNextVehicle(w);
}
if (w == NULL) {
// we failed to make the train short enough
SetDParam(0, v->unitnumber);
AddNewsItem(STR_TRAIN_TOO_LONG_AFTER_REPLACEMENT, NEWS_FLAGS(NM_SMALL, NF_VIEWPORT|NF_VEHICLE, NT_ADVICE, 0), v->index, 0);
break;
}
temp = w;
w = GetNextVehicle(w);
cost += DoCommand(0, 0, temp->index, 0, flags, CMD_SELL_VEH(temp->type));
}
}
if (IsLocalPlayer()) ShowCostOrIncomeAnimation(v->x_pos, v->y_pos, v->z_pos, cost); if (IsLocalPlayer()) ShowCostOrIncomeAnimation(v->x_pos, v->y_pos, v->z_pos, cost);
if (stopped) if (stopped)
@ -2085,8 +2114,10 @@ static const SaveLoad _train_desc[] = {
SLE_CONDVARX(offsetof(Vehicle,u)+offsetof(VehicleRail,pbs_status), SLE_UINT8, 2, 255), SLE_CONDVARX(offsetof(Vehicle,u)+offsetof(VehicleRail,pbs_status), SLE_UINT8, 2, 255),
SLE_CONDVARX(offsetof(Vehicle,u)+offsetof(VehicleRail,pbs_end_tile), SLE_UINT32, 2, 255), SLE_CONDVARX(offsetof(Vehicle,u)+offsetof(VehicleRail,pbs_end_tile), SLE_UINT32, 2, 255),
SLE_CONDVARX(offsetof(Vehicle,u)+offsetof(VehicleRail,pbs_end_trackdir), SLE_UINT8, 2, 255), SLE_CONDVARX(offsetof(Vehicle,u)+offsetof(VehicleRail,pbs_end_trackdir), SLE_UINT8, 2, 255),
// reserve extra space in savegame here. (currently 7 bytes) SLE_CONDVARX(offsetof(Vehicle,u)+offsetof(VehicleRail,shortest_platform[0]), SLE_UINT8, 2, 255), // added with 16.1, but was blank since 2
SLE_CONDARR(NullStruct,null,SLE_FILE_U8 | SLE_VAR_NULL, 7, 2, 255), SLE_CONDVARX(offsetof(Vehicle,u)+offsetof(VehicleRail,shortest_platform[1]), SLE_UINT8, 2, 255), // added with 16.1, but was blank since 2
// reserve extra space in savegame here. (currently 5 bytes)
SLE_CONDARR(NullStruct,null,SLE_FILE_U8 | SLE_VAR_NULL, 5, 2, 255),
SLE_END() SLE_END()
}; };

View File

@ -85,6 +85,14 @@ typedef struct VehicleRail {
byte pbs_status; byte pbs_status;
TileIndex pbs_end_tile; TileIndex pbs_end_tile;
Trackdir pbs_end_trackdir; Trackdir pbs_end_trackdir;
/**
* stuff to figure out how long a train should be. Used by autoreplace
* first byte holds the length of the shortest station. Updated each time order 0 is reached
* last byte is the shortest station reached this round though the orders. It can be invalidated by
* skip station and alike by setting it to 0. That way we will ensure that a complete loop is used to find the shortest station
*/
byte shortest_platform[2];
} VehicleRail; } VehicleRail;
enum { enum {
@ -177,7 +185,6 @@ struct Vehicle {
int32 x_pos; // coordinates int32 x_pos; // coordinates
int32 y_pos; int32 y_pos;
bool leave_depot_instantly; // NOSAVE: stores if the vehicle needs to leave the depot it just entered. Used by autoreplace
byte z_pos; byte z_pos;
byte direction; // facing byte direction; // facing
@ -248,6 +255,8 @@ struct Vehicle {
byte breakdown_chance; byte breakdown_chance;
byte build_year; byte build_year;
bool leave_depot_instantly; // NOSAVE: stores if the vehicle needs to leave the depot it just entered. Used by autoreplace
uint16 load_unload_time_rem; uint16 load_unload_time_rem;
int32 profit_this_year; int32 profit_this_year;

View File

@ -748,10 +748,10 @@ static void DrawEngineArrayInReplaceWindow(Window *w, int x, int y, int x2, int
static void ReplaceVehicleWndProc(Window *w, WindowEvent *e) static void ReplaceVehicleWndProc(Window *w, WindowEvent *e)
{ {
static const StringID _vehicle_type_names[4] = {STR_019F_TRAIN, STR_019C_ROAD_VEHICLE, STR_019E_SHIP,STR_019D_AIRCRAFT}; static const StringID _vehicle_type_names[4] = {STR_019F_TRAIN, STR_019C_ROAD_VEHICLE, STR_019E_SHIP,STR_019D_AIRCRAFT};
const Player *p = GetPlayer(_local_player);
switch (e->event) { switch (e->event) {
case WE_PAINT: { case WE_PAINT: {
const Player *p = GetPlayer(_local_player);
int pos = w->vscroll.pos; int pos = w->vscroll.pos;
int selected_id[2] = {-1,-1}; int selected_id[2] = {-1,-1};
int x = 1; int x = 1;
@ -839,6 +839,12 @@ static void ReplaceVehicleWndProc(Window *w, WindowEvent *e)
// now the actual drawing of the window itself takes place // now the actual drawing of the window itself takes place
SetDParam(0, _vehicle_type_names[WP(w, replaceveh_d).vehicletype - VEH_Train]); SetDParam(0, _vehicle_type_names[WP(w, replaceveh_d).vehicletype - VEH_Train]);
if (WP(w, replaceveh_d).vehicletype == VEH_Train) {
// set on/off for renew_keep_length
SetDParam(1, p->renew_keep_length ? STR_CONFIG_PATCHES_ON : STR_CONFIG_PATCHES_OFF);
}
DrawWindowWidgets(w); DrawWindowWidgets(w);
// sets up the string for the vehicle that is being replaced to // sets up the string for the vehicle that is being replaced to
@ -925,6 +931,9 @@ static void ReplaceVehicleWndProc(Window *w, WindowEvent *e)
ShowDropDownMenu(w, _rail_types_list, _railtype_selected_in_replace_gui, 15, 0, ~GetPlayer(_local_player)->avail_railtypes); ShowDropDownMenu(w, _rail_types_list, _railtype_selected_in_replace_gui, 15, 0, ~GetPlayer(_local_player)->avail_railtypes);
break; break;
} }
case 17: { /* toggle renew_keep_length */
DoCommandP(0, 5, p->renew_keep_length ? 0 : 1, NULL, CMD_REPLACE_VEHICLE);
} break;
case 4: { /* Start replacing */ case 4: { /* Start replacing */
EngineID veh_from = WP(w, replaceveh_d).sel_engine[0]; EngineID veh_from = WP(w, replaceveh_d).sel_engine[0];
EngineID veh_to = WP(w, replaceveh_d).sel_engine[1]; EngineID veh_to = WP(w, replaceveh_d).sel_engine[1];
@ -991,7 +1000,7 @@ static const Widget _replace_rail_vehicle_widgets[] = {
{ WWT_PANEL, RESIZE_TB, 14, 154, 277, 210, 221, STR_NULL, STR_REPLACE_HELP_RAILTYPE}, { WWT_PANEL, RESIZE_TB, 14, 154, 277, 210, 221, STR_NULL, STR_REPLACE_HELP_RAILTYPE},
{ WWT_CLOSEBOX, RESIZE_TB, 14, 278, 289, 210, 221, STR_0225, STR_REPLACE_HELP_RAILTYPE}, { WWT_CLOSEBOX, RESIZE_TB, 14, 278, 289, 210, 221, STR_0225, STR_REPLACE_HELP_RAILTYPE},
{ WWT_PANEL, RESIZE_TB, 14, 290, 305, 210, 221, STR_NULL, STR_NULL}, { WWT_PANEL, RESIZE_TB, 14, 290, 305, 210, 221, STR_NULL, STR_NULL},
{ WWT_PANEL, RESIZE_TB, 14, 317, 455, 198, 209, STR_NULL, STR_NULL}, { WWT_PUSHTXTBTN, RESIZE_TB, 14, 317, 455, 198, 209, STR_REPLACE_REMOVE_WAGON, STR_REPLACE_REMOVE_WAGON_HELP},
// end of train specific stuff // end of train specific stuff
{ WWT_RESIZEBOX, RESIZE_TB, 14, 444, 455, 210, 221, STR_NULL, STR_RESIZE_BUTTON}, { WWT_RESIZEBOX, RESIZE_TB, 14, 444, 455, 210, 221, STR_NULL, STR_RESIZE_BUTTON},
{ WIDGETS_END}, { WIDGETS_END},