# settings_gui.cpp

Go to the documentation of this file.

    00001 /* $Id: settings_gui.cpp 13199 2008-05-20 20:03:45Z rubidium $ */
    00002
    00005 #include "stdafx.h"
    00006 #include "openttd.h"
    00007 #include "currency.h"
    00008 #include "gui.h"
    00009 #include "window_gui.h"
    00010 #include "textbuf_gui.h"
    00011 #include "command_func.h"
    00012 #include "engine.h"
    00013 #include "screenshot.h"
    00014 #include "newgrf.h"
    00015 #include "network/network.h"
    00016 #include "town.h"
    00017 #include "variables.h"
    00018 #include "settings_internal.h"
    00019 #include "newgrf_townname.h"
    00020 #include "strings_func.h"
    00021 #include "functions.h"
    00022 #include "window_func.h"
    00023 #include "vehicle_base.h"
    00024 #include "core/alloc_func.hpp"
    00025 #include "string_func.h"
    00026 #include "gfx_func.h"
    00027 #include "waypoint.h"
    00028 #include "widgets/dropdown_type.h"
    00029 #include "widgets/dropdown_func.h"
    00030
    00031 #include "table/sprites.h"
    00032 #include "table/strings.h"
    00033
    00034 static const StringID _units_dropdown[] = {
    00035   STR_UNITS_IMPERIAL,
    00036   STR_UNITS_METRIC,
    00037   STR_UNITS_SI,
    00038   INVALID_STRING_ID
    00039 };
    00040
    00041 static const StringID _driveside_dropdown[] = {
    00042   STR_02E9_DRIVE_ON_LEFT,
    00043   STR_02EA_DRIVE_ON_RIGHT,
    00044   INVALID_STRING_ID
    00045 };
    00046
    00047 static const StringID _autosave_dropdown[] = {
    00048   STR_02F7_OFF,
    00049   STR_AUTOSAVE_1_MONTH,
    00050   STR_02F8_EVERY_3_MONTHS,
    00051   STR_02F9_EVERY_6_MONTHS,
    00052   STR_02FA_EVERY_12_MONTHS,
    00053   INVALID_STRING_ID,
    00054 };
    00055
    00056 static const StringID _designnames_dropdown[] = {
    00057   STR_02BE_DEFAULT,
    00058   STR_02BF_CUSTOM,
    00059   INVALID_STRING_ID
    00060 };
    00061
    00062 static StringID *BuildDynamicDropdown(StringID base, int num)
    00063 {
    00064   static StringID buf[32 + 1];
    00065   StringID *p = buf;
    00066   while (--num>=0) *p++ = base++;
    00067   *p = INVALID_STRING_ID;
    00068   return buf;
    00069 }
    00070
    00071 int _nb_orig_names = SPECSTR_TOWNNAME_LAST - SPECSTR_TOWNNAME_START + 1;
    00072 static StringID *_grf_names = NULL;
    00073 static int _nb_grf_names = 0;
    00074
    00075 void InitGRFTownGeneratorNames()
    00076 {
    00077   free(_grf_names);
    00078   _grf_names = GetGRFTownNameList();
    00079   _nb_grf_names = 0;
    00080   for (StringID *s = _grf_names; *s != INVALID_STRING_ID; s++) _nb_grf_names++;
    00081 }
    00082
    00083 static inline StringID TownName(int town_name)
    00084 {
    00085   if (town_name < _nb_orig_names) return STR_TOWNNAME_ORIGINAL_ENGLISH + town_name;
    00086   town_name -= _nb_orig_names;
    00087   if (town_name < _nb_grf_names) return _grf_names[town_name];
    00088   return STR_UNDEFINED;
    00089 }
    00090
    00091 static int GetCurRes()
    00092 {
    00093   int i;
    00094
    00095   for (i = 0; i != _num_resolutions; i++) {
    00096     if (_resolutions[i][0] == _screen.width &&
    00097         _resolutions[i][1] == _screen.height) {
    00098       break;
    00099     }
    00100   }
    00101   return i;
    00102 }
    00103
    00104 static inline bool RoadVehiclesAreBuilt()
    00105 {
    00106   const Vehicle* v;
    00107
    00108   FOR_ALL_VEHICLES(v) {
    00109     if (v->type == VEH_ROAD) return true;
    00110   }
    00111   return false;
    00112 }
    00113
    00114
    00115 enum GameOptionsWidgets {
    00116   GAMEOPT_CURRENCY_BTN    =  4,
    00117   GAMEOPT_DISTANCE_BTN    =  6,
    00118   GAMEOPT_ROADSIDE_BTN    =  8,
    00119   GAMEOPT_TOWNNAME_BTN    = 10,
    00120   GAMEOPT_AUTOSAVE_BTN    = 12,
    00121   GAMEOPT_VEHICLENAME_BTN = 14,
    00122   GAMEOPT_VEHICLENAME_SAVE,
    00123   GAMEOPT_LANG_BTN        = 17,
    00124   GAMEOPT_RESOLUTION_BTN  = 19,
    00125   GAMEOPT_FULLSCREEN,
    00126   GAMEOPT_SCREENSHOT_BTN  = 22,
    00127 };
    00128
    00134 static void ShowTownnameDropdown(Window *w, int sel)
    00135 {
    00136   typedef std::map<StringID, int, StringIDCompare> TownList;
    00137   TownList townnames;
    00138
    00139   /* Add and sort original townnames generators */
    00140   for (int i = 0; i < _nb_orig_names; i++) townnames[STR_TOWNNAME_ORIGINAL_ENGLISH + i] = i;
    00141
    00142   /* Add and sort newgrf townnames generators */
    00143   for (int i = 0; i < _nb_grf_names; i++) townnames[_grf_names[i]] = _nb_orig_names + i;
    00144
    00145   DropDownList *list = new DropDownList();
    00146   for (TownList::iterator it = townnames.begin(); it != townnames.end(); it++) {
    00147     list->push_back(new DropDownListStringItem((*it).first, (*it).second, !(_game_mode == GM_MENU || (*it).second == sel)));
    00148   }
    00149
    00150   ShowDropDownList(w, list, sel, GAMEOPT_TOWNNAME_BTN);
    00151 }
    00152
    00157 static void ShowLangDropdown(Window *w)
    00158 {
    00159   typedef std::map<StringID, int, StringIDCompare> LangList;
    00160
    00161   /* Sort language names */
    00162   LangList langs;
    00163   for (int i = 0; i < _dynlang.num; i++) langs[SPECSTR_LANGUAGE_START + i] = i;
    00164
    00165   DropDownList *list = new DropDownList();
    00166   for (LangList::iterator it = langs.begin(); it != langs.end(); it++) {
    00167     list->push_back(new DropDownListStringItem((*it).first, (*it).second, false));
    00168   }
    00169
    00170   ShowDropDownList(w, list, _dynlang.curr, GAMEOPT_LANG_BTN);
    00171 }
    00172
    00173 static void ShowCustCurrency();
    00174
    00175 static void GameOptionsWndProc(Window *w, WindowEvent *e)
    00176 {
    00177   switch (e->event) {
    00178     case WE_PAINT: {
    00179       int i;
    00180       StringID str = STR_02BE_DEFAULT;
    00181
    00182       w->SetWidgetDisabledState(GAMEOPT_VEHICLENAME_SAVE, !(_vehicle_design_names & 1));
    00183       if (!w->IsWidgetDisabled(GAMEOPT_VEHICLENAME_SAVE)) str = STR_02BF_CUSTOM;
    00184       SetDParam(0, str);
    00185       SetDParam(1, _currency_specs[_opt_ptr->currency].name);
    00186       SetDParam(2, STR_UNITS_IMPERIAL + _opt_ptr->units);
    00187       SetDParam(3, STR_02E9_DRIVE_ON_LEFT + _opt_ptr->road_side);
    00188       SetDParam(4, TownName(_opt_ptr->town_name));
    00189       SetDParam(5, _autosave_dropdown[_opt_ptr->autosave]);
    00190       SetDParam(6, SPECSTR_LANGUAGE_START + _dynlang.curr);
    00191       i = GetCurRes();
    00192       SetDParam(7, i == _num_resolutions ? STR_RES_OTHER : SPECSTR_RESOLUTION_START + i);
    00193       SetDParam(8, SPECSTR_SCREENSHOT_START + _cur_screenshot_format);
    00194       w->SetWidgetLoweredState(GAMEOPT_FULLSCREEN, _fullscreen);
    00195
    00196       DrawWindowWidgets(w);
    00197       DrawString(20, 175, STR_OPTIONS_FULLSCREEN, TC_FROMSTRING); // fullscreen
    00198     } break;
    00199
    00200     case WE_CLICK:
    00201       switch (e->we.click.widget) {
    00202         case GAMEOPT_CURRENCY_BTN: /* Setup currencies dropdown */
    00203           ShowDropDownMenu(w, BuildCurrencyDropdown(), _opt_ptr->currency, GAMEOPT_CURRENCY_BTN, _game_mode == GM_MENU ? 0 : ~GetMaskOfAllowedCurrencies(), 0);
    00204           break;
    00205
    00206         case GAMEOPT_DISTANCE_BTN: /* Setup distance unit dropdown */
    00207           ShowDropDownMenu(w, _units_dropdown, _opt_ptr->units, GAMEOPT_DISTANCE_BTN, 0, 0);
    00208           break;
    00209
    00210         case GAMEOPT_ROADSIDE_BTN: { /* Setup road-side dropdown */
    00211           int i = 0;
    00212
    00213           /* You can only change the drive side if you are in the menu or ingame with
    00214            * no vehicles present. In a networking game only the server can change it */
    00215           if ((_game_mode != GM_MENU && RoadVehiclesAreBuilt()) || (_networking && !_network_server))
    00216             i = (-1) ^ (1 << _opt_ptr->road_side); // disable the other value
    00217
    00218           ShowDropDownMenu(w, _driveside_dropdown, _opt_ptr->road_side, GAMEOPT_ROADSIDE_BTN, i, 0);
    00219         } break;
    00220
    00221         case GAMEOPT_TOWNNAME_BTN: /* Setup townname dropdown */
    00222           ShowTownnameDropdown(w, _opt_ptr->town_name);
    00223           break;
    00224
    00225         case GAMEOPT_AUTOSAVE_BTN: /* Setup autosave dropdown */
    00226           ShowDropDownMenu(w, _autosave_dropdown, _opt_ptr->autosave, GAMEOPT_AUTOSAVE_BTN, 0, 0);
    00227           break;
    00228
    00229         case GAMEOPT_VEHICLENAME_BTN: /* Setup customized vehicle-names dropdown */
    00230           ShowDropDownMenu(w, _designnames_dropdown, (_vehicle_design_names & 1) ? 1 : 0, GAMEOPT_VEHICLENAME_BTN, (_vehicle_design_names & 2) ? 0 : 2, 0);
    00231           break;
    00232
    00233         case GAMEOPT_VEHICLENAME_SAVE: /* Save customized vehicle-names to disk */
    00234           break;  // not implemented
    00235
    00236         case GAMEOPT_LANG_BTN: /* Setup interface language dropdown */
    00237           ShowLangDropdown(w);
    00238           break;
    00239
    00240         case GAMEOPT_RESOLUTION_BTN: /* Setup resolution dropdown */
    00241           ShowDropDownMenu(w, BuildDynamicDropdown(SPECSTR_RESOLUTION_START, _num_resolutions), GetCurRes(), GAMEOPT_RESOLUTION_BTN, 0, 0);
    00242           break;
    00243
    00244         case GAMEOPT_FULLSCREEN: /* Click fullscreen on/off */
    00245           /* try to toggle full-screen on/off */
    00246           if (!ToggleFullScreen(!_fullscreen)) {
    00247             ShowErrorMessage(INVALID_STRING_ID, STR_FULLSCREEN_FAILED, 0, 0);
    00248           }
    00249           w->SetWidgetLoweredState(GAMEOPT_FULLSCREEN, _fullscreen);
    00250           SetWindowDirty(w);
    00251           break;
    00252
    00253         case GAMEOPT_SCREENSHOT_BTN: /* Setup screenshot format dropdown */
    00254           ShowDropDownMenu(w, BuildDynamicDropdown(SPECSTR_SCREENSHOT_START, _num_screenshot_formats), _cur_screenshot_format, GAMEOPT_SCREENSHOT_BTN, 0, 0);
    00255           break;
    00256       }
    00257       break;
    00258
    00259     case WE_DROPDOWN_SELECT:
    00260       switch (e->we.dropdown.button) {
    00261         case GAMEOPT_VEHICLENAME_BTN: /* Vehicle design names */
    00262           if (e->we.dropdown.index == 0) {
    00263             DeleteCustomEngineNames();
    00264             MarkWholeScreenDirty();
    00265           } else if (!(_vehicle_design_names & 1)) {
    00266             LoadCustomEngineNames();
    00267             MarkWholeScreenDirty();
    00268           }
    00269           break;
    00270
    00271         case GAMEOPT_CURRENCY_BTN: /* Currency */
    00272           if (e->we.dropdown.index == CUSTOM_CURRENCY_ID) ShowCustCurrency();
    00273           _opt_ptr->currency = e->we.dropdown.index;
    00274           MarkWholeScreenDirty();
    00275           break;
    00276
    00277         case GAMEOPT_DISTANCE_BTN: /* Measuring units */
    00278           _opt_ptr->units = e->we.dropdown.index;
    00279           MarkWholeScreenDirty();
    00280           break;
    00281
    00282         case GAMEOPT_ROADSIDE_BTN: /* Road side */
    00283           if (_opt_ptr->road_side != e->we.dropdown.index) { // only change if setting changed
    00284             DoCommandP(0, e->we.dropdown.index, 0, NULL, CMD_SET_ROAD_DRIVE_SIDE | CMD_MSG(STR_00B4_CAN_T_DO_THIS));
    00285             MarkWholeScreenDirty();
    00286           }
    00287           break;
    00288
    00289         case GAMEOPT_TOWNNAME_BTN: /* Town names */
    00290           if (_game_mode == GM_MENU) {
    00291             _opt_ptr->town_name = e->we.dropdown.index;
    00292             InvalidateWindow(WC_GAME_OPTIONS, 0);
    00293           }
    00294           break;
    00295
    00296         case GAMEOPT_AUTOSAVE_BTN: /* Autosave options */
    00297           _opt.autosave = _opt_newgame.autosave = e->we.dropdown.index;
    00298           SetWindowDirty(w);
    00299           break;
    00300
    00301         case GAMEOPT_LANG_BTN: /* Change interface language */
    00302           ReadLanguagePack(e->we.dropdown.index);
    00303           CheckForMissingGlyphsInLoadedLanguagePack();
    00304           UpdateAllStationVirtCoord();
    00305           UpdateAllWaypointSigns();
    00306           MarkWholeScreenDirty();
    00307           break;
    00308
    00309         case GAMEOPT_RESOLUTION_BTN: /* Change resolution */
    00310           if (e->we.dropdown.index < _num_resolutions && ChangeResInGame(_resolutions[e->we.dropdown.index][0],_resolutions[e->we.dropdown.index][1]))
    00311             SetWindowDirty(w);
    00312           break;
    00313
    00314         case GAMEOPT_SCREENSHOT_BTN: /* Change screenshot format */
    00315           SetScreenshotFormat(e->we.dropdown.index);
    00316           SetWindowDirty(w);
    00317           break;
    00318       }
    00319       break;
    00320
    00321     case WE_DESTROY:
    00322       DeleteWindowById(WC_CUSTOM_CURRENCY, 0);
    00323       break;
    00324   }
    00325
    00326 }
    00327
    00334 CommandCost CmdSetRoadDriveSide(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
    00335 {
    00336   /* Check boundaries and you can only change this if NO vehicles have been built yet,
    00337    * except in the intro-menu where of course it's always possible to do so. */
    00338   if (p1 > 1 || (_game_mode != GM_MENU && RoadVehiclesAreBuilt())) return CMD_ERROR;
    00339
    00340   if (flags & DC_EXEC) {
    00341     _opt_ptr->road_side = p1;
    00342     InvalidateWindow(WC_GAME_OPTIONS,0);
    00343   }
    00344   return CommandCost();
    00345 }
    00346
    00347 static const Widget _game_options_widgets[] = {
    00348 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                          STR_018B_CLOSE_WINDOW},
    00349 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   369,     0,    13, STR_00B1_GAME_OPTIONS,             STR_018C_WINDOW_TITLE_DRAG_THIS},
    00350 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   369,    14,   238, 0x0,                               STR_NULL},
    00351 {      WWT_FRAME,   RESIZE_NONE,    14,    10,   179,    20,    55, STR_02E0_CURRENCY_UNITS,           STR_NULL},
    00352 { WWT_DROPDOWNIN,   RESIZE_NONE,    14,    20,   169,    34,    45, STR_02E1,                          STR_02E2_CURRENCY_UNITS_SELECTION},
    00353 {      WWT_FRAME,   RESIZE_NONE,    14,   190,   359,    20,    55, STR_MEASURING_UNITS,               STR_NULL},
    00354 { WWT_DROPDOWNIN,   RESIZE_NONE,    14,   200,   349,    34,    45, STR_02E4,                          STR_MEASURING_UNITS_SELECTION},
    00355 {      WWT_FRAME,   RESIZE_NONE,    14,    10,   179,    62,    97, STR_02E6_ROAD_VEHICLES,            STR_NULL},
    00356 { WWT_DROPDOWNIN,   RESIZE_NONE,    14,    20,   169,    76,    87, STR_02E7,                          STR_02E8_SELECT_SIDE_OF_ROAD_FOR},
    00357 {      WWT_FRAME,   RESIZE_NONE,    14,   190,   359,    62,    97, STR_02EB_TOWN_NAMES,               STR_NULL},
    00358 { WWT_DROPDOWNIN,   RESIZE_NONE,    14,   200,   349,    76,    87, STR_02EC,                          STR_02ED_SELECT_STYLE_OF_TOWN_NAMES},
    00359 {      WWT_FRAME,   RESIZE_NONE,    14,    10,   179,   104,   139, STR_02F4_AUTOSAVE,                 STR_NULL},
    00360 { WWT_DROPDOWNIN,   RESIZE_NONE,    14,    20,   169,   118,   129, STR_02F5,                          STR_02F6_SELECT_INTERVAL_BETWEEN},
    00361
    00362 {      WWT_FRAME,   RESIZE_NONE,    14,    10,   359,   194,   228, STR_02BC_VEHICLE_DESIGN_NAMES,     STR_NULL},
    00363 { WWT_DROPDOWNIN,   RESIZE_NONE,    14,    20,   119,   207,   218, STR_02BD,                          STR_02C1_VEHICLE_DESIGN_NAMES_SELECTION},
    00364 {    WWT_TEXTBTN,   RESIZE_NONE,    14,   130,   349,   207,   218, STR_02C0_SAVE_CUSTOM_NAMES,        STR_02C2_SAVE_CUSTOMIZED_VEHICLE},
    00365
    00366 {      WWT_FRAME,   RESIZE_NONE,    14,   190,   359,   104,   139, STR_OPTIONS_LANG,                  STR_NULL},
    00367 { WWT_DROPDOWNIN,   RESIZE_NONE,    14,   200,   349,   118,   129, STR_OPTIONS_LANG_CBO,              STR_OPTIONS_LANG_TIP},
    00368
    00369 {      WWT_FRAME,   RESIZE_NONE,    14,    10,   179,   146,   190, STR_OPTIONS_RES,                   STR_NULL},
    00370 { WWT_DROPDOWNIN,   RESIZE_NONE,    14,    20,   169,   160,   171, STR_OPTIONS_RES_CBO,               STR_OPTIONS_RES_TIP},
    00371 {    WWT_TEXTBTN,   RESIZE_NONE,    14,   149,   169,   176,   184, STR_EMPTY,                         STR_OPTIONS_FULLSCREEN_TIP},
    00372
    00373 {      WWT_FRAME,   RESIZE_NONE,    14,   190,   359,   146,   190, STR_OPTIONS_SCREENSHOT_FORMAT,     STR_NULL},
    00374 { WWT_DROPDOWNIN,   RESIZE_NONE,    14,   200,   349,   160,   171, STR_OPTIONS_SCREENSHOT_FORMAT_CBO, STR_OPTIONS_SCREENSHOT_FORMAT_TIP},
    00375
    00376 {   WIDGETS_END},
    00377 };
    00378
    00379 static const WindowDesc _game_options_desc = {
    00380   WDP_CENTER, WDP_CENTER, 370, 239, 370, 239,
    00381   WC_GAME_OPTIONS, WC_NONE,
    00382   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
    00383   _game_options_widgets,
    00384   GameOptionsWndProc
    00385 };
    00386
    00387
    00388 void ShowGameOptions()
    00389 {
    00390   DeleteWindowById(WC_GAME_OPTIONS, 0);
    00391   AllocateWindowDesc(&_game_options_desc);
    00392 }
    00393
    00394 struct GameSettingData {
    00395   int16 min;
    00396   int16 max;
    00397   int16 step;
    00398   StringID str;
    00399 };
    00400
    00401 static const GameSettingData _game_setting_info[] = {
    00402   {  0,   7,  1, STR_NULL},
    00403   {  0,   3,  1, STR_6830_IMMEDIATE},
    00404   {  0,   3,  1, STR_NUM_VERY_LOW},
    00405   {  0,   4,  1, STR_NONE},
    00406   {100, 500, 50, STR_NULL},
    00407   {  2,   4,  1, STR_NULL},
    00408   {  0,   2,  1, STR_6820_LOW},
    00409   {  0,   4,  1, STR_681B_VERY_SLOW},
    00410   {  0,   2,  1, STR_6820_LOW},
    00411   {  0,   2,  1, STR_6823_NONE},
    00412   {  0,   3,  1, STR_6826_X1_5},
    00413   {  0,   2,  1, STR_6820_LOW},
    00414   {  0,   3,  1, STR_682A_VERY_FLAT},
    00415   {  0,   3,  1, STR_VERY_LOW},
    00416   {  0,   1,  1, STR_682E_STEADY},
    00417   {  0,   1,  1, STR_6834_AT_END_OF_LINE_AND_AT_STATIONS},
    00418   {  0,   1,  1, STR_6836_OFF},
    00419   {  0,   2,  1, STR_PERMISSIVE},
    00420 };
    00421
    00422 /*
    00423  * A: competitors
    00424  * B: start time in months / 3
    00425  * C: town count (3 = high, 0 = very low)
    00426  * D: industry count (4 = high, 0 = none)
    00427  * E: inital loan / 1000 (in GBP)
    00428  * F: interest rate
    00429  * G: running costs (0 = low, 2 = high)
    00430  * H: construction speed of competitors (0 = very slow, 4 = very fast)
    00431  * I: intelligence (0-2)
    00432  * J: breakdowns (0 = off, 2 = normal)
    00433  * K: subsidy multiplier (0 = 1.5, 3 = 4.0)
    00434  * L: construction cost (0-2)
    00435  * M: terrain type (0 = very flat, 3 = mountainous)
    00436  * N: amount of water (0 = very low, 3 = high)
    00437  * O: economy (0 = steady, 1 = fluctuating)
    00438  * P: Train reversing (0 = end of line + stations, 1 = end of line)
    00439  * Q: disasters
    00440  * R: area restructuring (0 = permissive, 2 = hostile)
    00441  */
    00442 static const GDType _default_game_diff[3][GAME_DIFFICULTY_NUM] = { /*
    00443    A, B, C, D,   E, F, G, H, I, J, K, L, M, N, O, P, Q, R*/
    00444   {2, 2, 2, 4, 300, 2, 0, 2, 0, 1, 2, 0, 1, 0, 0, 0, 0, 0},
    00445   {4, 1, 2, 3, 150, 3, 1, 3, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1},
    00446   {7, 0, 3, 3, 100, 4, 1, 3, 2, 2, 0, 2, 3, 2, 1, 1, 1, 2},
    00447 };
    00448
    00449 void SetDifficultyLevel(int mode, GameOptions *gm_opt)
    00450 {
    00451   int i;
    00452   assert(mode <= 3);
    00453
    00454   gm_opt->diff_level = mode;
    00455   if (mode != 3) { // not custom
    00456     for (i = 0; i != GAME_DIFFICULTY_NUM; i++)
    00457       ((GDType*)&gm_opt->diff)[i] = _default_game_diff[mode][i];
    00458   }
    00459 }
    00460
    00465 void CheckDifficultyLevels()
    00466 {
    00467   if (_opt_newgame.diff_level != 3) {
    00468     SetDifficultyLevel(_opt_newgame.diff_level, &_opt_newgame);
    00469   } else {
    00470     for (uint i = 0; i < GAME_DIFFICULTY_NUM; i++) {
    00471       GDType *diff = ((GDType*)&_opt_newgame.diff) + i;
    00472       *diff = Clamp(*diff, _game_setting_info[i].min, _game_setting_info[i].max);
    00473       *diff -= *diff % _game_setting_info[i].step;
    00474     }
    00475   }
    00476 }
    00477
    00478 extern void StartupEconomy();
    00479
    00480 enum {
    00481   GAMEDIFF_WND_TOP_OFFSET = 45,
    00482   GAMEDIFF_WND_ROWSIZE    = 9
    00483 };
    00484
    00485 /* Temporary holding place of values in the difficulty window until 'Save' is clicked */
    00486 static GameOptions _opt_mod_temp;
    00487 // 0x383E = (1 << 13) | (1 << 12) | (1 << 11) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1)
    00488 #define DIFF_INGAME_DISABLED_BUTTONS 0x383E
    00489
    00490 #define NO_SETTINGS_BUTTON 0xFF
    00491
    00493 struct difficulty_d {
    00494   bool clicked_increase;
    00495   uint8 clicked_button;
    00496   uint8 timeout;
    00497 };
    00498 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(difficulty_d));
    00499
    00500 /* Names of the game difficulty settings window */
    00501 enum GameDifficultyWidgets {
    00502   GDW_CLOSEBOX = 0,
    00503   GDW_CAPTION,
    00504   GDW_UPPER_BG,
    00505   GDW_LVL_EASY,
    00506   GDW_LVL_MEDIUM,
    00507   GDW_LVL_HARD,
    00508   GDW_LVL_CUSTOM,
    00509   GDW_HIGHSCORE,
    00510   GDW_SETTING_BG,
    00511   GDW_LOWER_BG,
    00512   GDW_ACCEPT,
    00513   GDW_CANCEL,
    00514 };
    00515
    00516 static void GameDifficultyWndProc(Window *w, WindowEvent *e)
    00517 {
    00518   difficulty_d *diffic_d = &WP(w, difficulty_d);
    00519   switch (e->event) {
    00520     case WE_CREATE:
    00521       diffic_d->clicked_increase = false;
    00522       diffic_d->clicked_button = NO_SETTINGS_BUTTON;
    00523       diffic_d->timeout = 0;
    00524       /* Hide the closebox to make sure that the user aborts or confirms his changes */
    00525       w->HideWidget(GDW_CLOSEBOX);
    00526       w->widget[GDW_CAPTION].left = 0;
    00527       /* Setup disabled buttons when creating window
    00528        * disable all other difficulty buttons during gameplay except for 'custom' */
    00529       w->SetWidgetsDisabledState(_game_mode == GM_NORMAL,
    00530         GDW_LVL_EASY,
    00531         GDW_LVL_MEDIUM,
    00532         GDW_LVL_HARD,
    00533         GDW_LVL_CUSTOM,
    00534         WIDGET_LIST_END);
    00535       w->SetWidgetDisabledState(GDW_HIGHSCORE, _game_mode == GM_EDITOR || _networking); // highscore chart in multiplayer
    00536       w->SetWidgetDisabledState(GDW_ACCEPT, _networking && !_network_server); // Save-button in multiplayer (and if client)
    00537       w->LowerWidget(GDW_LVL_EASY + _opt_mod_temp.diff_level);
    00538       break;
    00539
    00540     case WE_PAINT: {
    00541       DrawWindowWidgets(w);
    00542
    00543       /* XXX - Disabled buttons in normal gameplay or during muliplayer as non server.
    00544        *       Bitshifted for each button to see if that bit is set. If it is set, the
    00545        *       button is disabled */
    00546       uint32 disabled = 0;
    00547       if (_networking && !_network_server) {
    00548         disabled = MAX_UVALUE(uint32); // Disable all
    00549       } else if (_game_mode == GM_NORMAL) {
    00550         disabled = DIFF_INGAME_DISABLED_BUTTONS;
    00551       }
    00552
    00553       int value;
    00554       int y = GAMEDIFF_WND_TOP_OFFSET;
    00555       for (uint i = 0; i != GAME_DIFFICULTY_NUM; i++) {
    00556         const GameSettingData *gsd = &_game_setting_info[i];
    00557         value = ((GDType*)&_opt_mod_temp.diff)[i];
    00558
    00559         DrawArrowButtons(5, y, 3,
    00560             (diffic_d->clicked_button == i) ? 1 + !!diffic_d->clicked_increase : 0,
    00561             !(HasBit(disabled, i) || gsd->min == value),
    00562             !(HasBit(disabled, i) || gsd->max == value));
    00563
    00564         value += _game_setting_info[i].str;
    00565         if (i == 4) value *= 1000; // XXX - handle currency option
    00566         SetDParam(0, value);
    00567         DrawString(30, y, STR_6805_MAXIMUM_NO_COMPETITORS + i, TC_FROMSTRING);
    00568
    00569         y += GAMEDIFF_WND_ROWSIZE + 2; // space items apart a bit
    00570       }
    00571     } break;
    00572
    00573     case WE_CLICK:
    00574       switch (e->we.click.widget) {
    00575         case GDW_SETTING_BG: { /* Difficulty settings widget, decode click */
    00576           /* Don't allow clients to make any changes */
    00577           if (_networking && !_network_server) return;
    00578
    00579           const int x = e->we.click.pt.x - 5;
    00580           if (!IsInsideMM(x, 0, 21)) // Button area
    00581             return;
    00582
    00583           const int y = e->we.click.pt.y - GAMEDIFF_WND_TOP_OFFSET;
    00584           if (y < 0) return;
    00585
    00586           /* Get button from Y coord. */
    00587           const uint8 btn = y / (GAMEDIFF_WND_ROWSIZE + 2);
    00588           if (btn >= GAME_DIFFICULTY_NUM || y % (GAMEDIFF_WND_ROWSIZE + 2) >= 9)
    00589             return;
    00590
    00591           /* Clicked disabled button? */
    00592           if (_game_mode == GM_NORMAL && HasBit(DIFF_INGAME_DISABLED_BUTTONS, btn))
    00593             return;
    00594
    00595           diffic_d->timeout = 5;
    00596
    00597           int16 val = ((GDType*)&_opt_mod_temp.diff)[btn];
    00598
    00599           const GameSettingData *info = &_game_setting_info[btn]; // get information about the difficulty setting
    00600           if (x >= 10) {
    00601             /* Increase button clicked */
    00602             val = min(val + info->step, info->max);
    00603             diffic_d->clicked_increase = true;
    00604           } else {
    00605             /* Decrease button clicked */
    00606             val -= info->step;
    00607             val = max(val,  info->min);
    00608             diffic_d->clicked_increase = false;
    00609           }
    00610           diffic_d->clicked_button = btn;
    00611
    00612           /* save value in temporary variable */
    00613           ((GDType*)&_opt_mod_temp.diff)[btn] = val;
    00614           w->RaiseWidget(GDW_LVL_EASY + _opt_mod_temp.diff_level);
    00615           SetDifficultyLevel(3, &_opt_mod_temp); // set difficulty level to custom
    00616           w->LowerWidget(GDW_LVL_CUSTOM);
    00617           SetWindowDirty(w);
    00618         } break;
    00619
    00620         case GDW_LVL_EASY:
    00621         case GDW_LVL_MEDIUM:
    00622         case GDW_LVL_HARD:
    00623         case GDW_LVL_CUSTOM:
    00624           /* temporarily change difficulty level */
    00625           w->RaiseWidget(GDW_LVL_EASY + _opt_mod_temp.diff_level);
    00626           SetDifficultyLevel(e->we.click.widget - GDW_LVL_EASY, &_opt_mod_temp);
    00627           w->LowerWidget(GDW_LVL_EASY + _opt_mod_temp.diff_level);
    00628           SetWindowDirty(w);
    00629           break;
    00630
    00631         case GDW_HIGHSCORE: // Highscore Table
    00632           ShowHighscoreTable(_opt_mod_temp.diff_level, -1);
    00633           break;
    00634
    00635         case GDW_ACCEPT: { // Save button - save changes
    00636           GDType btn, val;
    00637           for (btn = 0; btn != GAME_DIFFICULTY_NUM; btn++) {
    00638             val = ((GDType*)&_opt_mod_temp.diff)[btn];
    00639             /* if setting has changed, change it */
    00640             if (val != ((GDType*)&_opt_ptr->diff)[btn])
    00641               DoCommandP(0, btn, val, NULL, CMD_CHANGE_DIFFICULTY_LEVEL);
    00642           }
    00643           DoCommandP(0, UINT_MAX, _opt_mod_temp.diff_level, NULL, CMD_CHANGE_DIFFICULTY_LEVEL);
    00644           DeleteWindow(w);
    00645           /* If we are in the editor, we should reload the economy.
    00646            * This way when you load a game, the max loan and interest rate
    00647            * are loaded correctly. */
    00648           if (_game_mode == GM_EDITOR) StartupEconomy();
    00649           break;
    00650         }
    00651
    00652         case GDW_CANCEL: // Cancel button - close window, abandon changes
    00653           DeleteWindow(w);
    00654           break;
    00655       } break;
    00656
    00657     case WE_MOUSELOOP: /* Handle the visual 'clicking' of the buttons */
    00658       if (diffic_d->timeout != 0) {
    00659         diffic_d->timeout--;
    00660         if (diffic_d->timeout == 0) diffic_d->clicked_button = NO_SETTINGS_BUTTON;
    00661         SetWindowDirty(w);
    00662       }
    00663       break;
    00664   }
    00665 }
    00666 #undef DIFF_INGAME_DISABLED_BUTTONS
    00667
    00668 /* Widget definition for the game difficulty settings window */
    00669 static const Widget _game_difficulty_widgets[] = {
    00670 {   WWT_CLOSEBOX,   RESIZE_NONE,    10,     0,    10,     0,    13, STR_00C5,                     STR_018B_CLOSE_WINDOW},           // GDW_CLOSEBOX
    00671 {    WWT_CAPTION,   RESIZE_NONE,    10,    11,   369,     0,    13, STR_6800_DIFFICULTY_LEVEL,    STR_018C_WINDOW_TITLE_DRAG_THIS}, // GDW_CAPTION
    00672 {      WWT_PANEL,   RESIZE_NONE,    10,     0,   369,    14,    41, 0x0,                          STR_NULL},                        // GDW_UPPER_BG
    00673 { WWT_PUSHTXTBTN,   RESIZE_NONE,     3,    10,    96,    16,    27, STR_6801_EASY,                STR_NULL},                        // GDW_LVL_EASY
    00674 { WWT_PUSHTXTBTN,   RESIZE_NONE,     3,    97,   183,    16,    27, STR_6802_MEDIUM,              STR_NULL},                        // GDW_LVL_MEDIUM
    00675 { WWT_PUSHTXTBTN,   RESIZE_NONE,     3,   184,   270,    16,    27, STR_6803_HARD,                STR_NULL},                        // GDW_LVL_HARD
    00676 { WWT_PUSHTXTBTN,   RESIZE_NONE,     3,   271,   357,    16,    27, STR_6804_CUSTOM,              STR_NULL},                        // GDW_LVL_CUSTOM
    00677 {    WWT_TEXTBTN,   RESIZE_NONE,     6,    10,   357,    28,    39, STR_6838_SHOW_HI_SCORE_CHART, STR_NULL},                        // GDW_HIGHSCORE
    00678 {      WWT_PANEL,   RESIZE_NONE,    10,     0,   369,    42,   262, 0x0,                          STR_NULL},                        // GDW_SETTING_BG
    00679 {      WWT_PANEL,   RESIZE_NONE,    10,     0,   369,   263,   278, 0x0,                          STR_NULL},                        // GDW_LOWER_BG
    00680 { WWT_PUSHTXTBTN,   RESIZE_NONE,     3,   105,   185,   265,   276, STR_OPTIONS_SAVE_CHANGES,     STR_NULL},                        // GDW_ACCEPT
    00681 { WWT_PUSHTXTBTN,   RESIZE_NONE,     3,   186,   266,   265,   276, STR_012E_CANCEL,              STR_NULL},                        // GDW_CANCEL
    00682 {   WIDGETS_END},
    00683 };
    00684
    00685 /* Window definition for the game difficulty settings window */
    00686 static const WindowDesc _game_difficulty_desc = {
    00687   WDP_CENTER, WDP_CENTER, 370, 279, 370, 279,
    00688   WC_GAME_OPTIONS, WC_NONE,
    00689   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
    00690   _game_difficulty_widgets,
    00691   GameDifficultyWndProc
    00692 };
    00693
    00694 void ShowGameDifficulty()
    00695 {
    00696   DeleteWindowById(WC_GAME_OPTIONS, 0);
    00697   /* Copy current settings (ingame or in intro) to temporary holding place
    00698    * change that when setting stuff, copy back on clicking 'OK' */
    00699   _opt_mod_temp = *_opt_ptr;
    00700   AllocateWindowDesc(&_game_difficulty_desc);
    00701 }
    00702
    00703 static const char *_patches_ui[] = {
    00704   "vehicle_speed",
    00705   "status_long_date",
    00706   "show_finances",
    00707   "autoscroll",
    00708   "reverse_scroll",
    00709   "smooth_scroll",
    00710   "errmsg_duration",
    00711   "toolbar_pos",
    00712   "measure_tooltip",
    00713   "window_snap_radius",
    00714   "invisible_trees",
    00715   "population_in_label",
    00716   "link_terraform_toolbar",
    00717   "liveries",
    00718   "prefer_teamchat",
    00719   /* While the horizontal scrollwheel scrolling is written as general code, only
    00720    *  the cocoa (OSX) driver generates input for it.
    00721    *  Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
    00722   "scrollwheel_scrolling",
    00723   "scrollwheel_multiplier",
    00724 #ifdef __APPLE__
    00725   /* We might need to emulate a right mouse button on mac */
    00726   "right_mouse_btn_emulation",
    00727 #endif
    00728   "pause_on_newgame",
    00729   "advanced_vehicle_list",
    00730   "loading_indicators",
    00731   "timetable_in_ticks",
    00732   "default_rail_type",
    00733   "always_build_infrastructure",
    00734 };
    00735
    00736 static const char *_patches_construction[] = {
    00737   "build_on_slopes",
    00738   "autoslope",
    00739   "extra_dynamite",
    00740   "longbridges",
    00741   "signal_side",
    00742   "always_small_airport",
    00743   "enable_signal_gui",
    00744   "drag_signals_density",
    00745   "oil_refinery_limit",
    00746   "semaphore_build_before",
    00747 };
    00748
    00749 static const char *_patches_stations[] = {
    00750   "join_stations",
    00751   "full_load_any",
    00752   "improved_load",
    00753   "selectgoods",
    00754   "new_nonstop",
    00755   "nonuniform_stations",
    00756   "station_spread",
    00757   "serviceathelipad",
    00758   "modified_catchment",
    00759   "gradual_loading",
    00760   "road_stop_on_town_road",
    00761   "adjacent_stations",
    00762 };
    00763
    00764 static const char *_patches_economy[] = {
    00765   "inflation",
    00766   "raw_industry_construction",
    00767   "multiple_industry_per_town",
    00768   "same_industry_close",
    00769   "bribe",
    00770   "exclusive_rights",
    00771   "give_money",
    00772   "colored_news_year",
    00773   "ending_year",
    00774   "smooth_economy",
    00775   "allow_shares",
    00776   "town_layout",
    00777   "mod_road_rebuild",
    00778   "town_growth_rate",
    00779   "larger_towns",
    00780   "initial_city_size",
    00781 };
    00782
    00783 static const char *_patches_ai[] = {
    00784   "ainew_active",
    00785   "ai_in_multiplayer",
    00786   "ai_disable_veh_train",
    00787   "ai_disable_veh_roadveh",
    00788   "ai_disable_veh_aircraft",
    00789   "ai_disable_veh_ship",
    00790 };
    00791
    00792 static const char *_patches_vehicles[] = {
    00793   "realistic_acceleration",
    00794   "forbid_90_deg",
    00795   "mammoth_trains",
    00796   "gotodepot",
    00797   "roadveh_queue",
    00798   "pathfinder_for_trains",
    00799   "pathfinder_for_roadvehs",
    00800   "pathfinder_for_ships",
    00801   "train_income_warn",
    00802   "order_review_system",
    00803   "never_expire_vehicles",
    00804   "lost_train_warn",
    00805   "autorenew",
    00806   "autorenew_months",
    00807   "autorenew_money",
    00808   "max_trains",
    00809   "max_roadveh",
    00810   "max_aircraft",
    00811   "max_ships",
    00812   "servint_ispercent",
    00813   "servint_trains",
    00814   "servint_roadveh",
    00815   "servint_ships",
    00816   "servint_aircraft",
    00817   "no_servicing_if_no_breakdowns",
    00818   "wagon_speed_limits",
    00819   "disable_elrails",
    00820   "freight_trains",
    00821   "plane_speed",
    00822   "timetabling",
    00823 };
    00824
    00825 struct PatchEntry {
    00826   const SettingDesc *setting;
    00827   uint index;
    00828 };
    00829
    00830 struct PatchPage {
    00831   const char **names;
    00832   PatchEntry *entries;
    00833   byte num;
    00834 };
    00835
    00836 /* PatchPage holds the categories, the number of elements in each category
    00837  * and (in NULL) a dynamic array of settings based on the string-representations
    00838  * of the settings. This way there is no worry about indeces, and such */
    00839 static PatchPage _patches_page[] = {
    00840   {_patches_ui,           NULL, lengthof(_patches_ui)},
    00841   {_patches_construction, NULL, lengthof(_patches_construction)},
    00842   {_patches_vehicles,     NULL, lengthof(_patches_vehicles)},
    00843   {_patches_stations,     NULL, lengthof(_patches_stations)},
    00844   {_patches_economy,      NULL, lengthof(_patches_economy)},
    00845   {_patches_ai,           NULL, lengthof(_patches_ai)},
    00846 };
    00847
    00848 enum PatchesSelectionWidgets {
    00849   PATCHSEL_OPTIONSPANEL = 3,
    00850   PATCHSEL_INTERFACE,
    00851   PATCHSEL_CONSTRUCTION,
    00852   PATCHSEL_VEHICLES,
    00853   PATCHSEL_STATIONS,
    00854   PATCHSEL_ECONOMY,
    00855   PATCHSEL_COMPETITORS
    00856 };
    00857
    00861 static void PatchesSelectionWndProc(Window *w, WindowEvent *e)
    00862 {
    00863   static Patches *patches_ptr;
    00864   static int patches_max = 0;
    00865
    00866   switch (e->event) {
    00867     case WE_CREATE: {
    00868       static bool first_time = true;
    00869
    00870       patches_ptr = (_game_mode == GM_MENU) ? &_patches_newgame : &_patches;
    00871
    00872       /* Build up the dynamic settings-array only once per OpenTTD session */
    00873       if (first_time) {
    00874         PatchPage *page;
    00875         for (page = &_patches_page[0]; page != endof(_patches_page); page++) {
    00876           uint i;
    00877
    00878           if (patches_max < page->num) patches_max = page->num;
    00879
    00880           page->entries = MallocT<PatchEntry>(page->num);
    00881           for (i = 0; i != page->num; i++) {
    00882             uint index;
    00883             const SettingDesc *sd = GetPatchFromName(page->names[i], &index);
    00884             assert(sd != NULL);
    00885
    00886             page->entries[i].setting = sd;
    00887             page->entries[i].index = index;
    00888           }
    00889         }
    00890         first_time = false;
    00891       }
    00892
    00893       /* Resize the window to fit the largest patch tab */
    00894       ResizeWindowForWidget(w, PATCHSEL_OPTIONSPANEL, 0, patches_max * 11);
    00895
    00896       /* Recentre the window for the new size */
    00897       w->top = w->top - (patches_max * 11) / 2;
    00898
    00899       w->LowerWidget(4);
    00900     } break;
    00901
    00902     case WE_PAINT: {
    00903       int x, y;
    00904       const PatchPage *page = &_patches_page[WP(w, def_d).data_1];
    00905       uint i;
    00906
    00907       /* Set up selected category */
    00908       DrawWindowWidgets(w);
    00909
    00910       x = 5;
    00911       y = 47;
    00912       for (i = 0; i != page->num; i++) {
    00913         const SettingDesc *sd = page->entries[i].setting;
    00914         const SettingDescBase *sdb = &sd->desc;
    00915         const void *var = GetVariableAddress(patches_ptr, &sd->save);
    00916         bool editable = true;
    00917         bool disabled = false;
    00918
    00919         // We do not allow changes of some items when we are a client in a networkgame
    00920         if (!(sd->save.conv & SLF_NETWORK_NO) && _networking && !_network_server) editable = false;
    00921         if ((sdb->flags & SGF_NETWORK_ONLY) && !_networking) editable = false;
    00922         if ((sdb->flags & SGF_NO_NETWORK) && _networking) editable = false;
    00923
    00924         if (sdb->cmd == SDT_BOOLX) {
    00925           static const int _bool_ctabs[2][2] = {{9, 4}, {7, 6}};
    00926           /* Draw checkbox for boolean-value either on/off */
    00927           bool on = (*(bool*)var);
    00928
    00929           DrawFrameRect(x, y, x + 19, y + 8, _bool_ctabs[!!on][!!editable], on ? FR_LOWERED : FR_NONE);
    00930           SetDParam(0, on ? STR_CONFIG_PATCHES_ON : STR_CONFIG_PATCHES_OFF);
    00931         } else {
    00932           int32 value;
    00933
    00934           value = (int32)ReadValue(var, sd->save.conv);
    00935
    00936           /* Draw [<][>] boxes for settings of an integer-type */
    00937           DrawArrowButtons(x, y, 3, WP(w, def_d).data_2 - (i * 2), (editable && value != sdb->min), (editable && value != sdb->max));
    00938
    00939           disabled = (value == 0) && (sdb->flags & SGF_0ISDISABLED);
    00940           if (disabled) {
    00941             SetDParam(0, STR_CONFIG_PATCHES_DISABLED);
    00942           } else {
    00943             if (sdb->flags & SGF_CURRENCY) {
    00944               SetDParam(0, STR_CONFIG_PATCHES_CURRENCY);
    00945             } else if (sdb->flags & SGF_MULTISTRING) {
    00946               SetDParam(0, sdb->str + value + 1);
    00947             } else {
    00948               SetDParam(0, (sdb->flags & SGF_NOCOMMA) ? STR_CONFIG_PATCHES_INT32 : STR_7024);
    00949             }
    00950             SetDParam(1, value);
    00951           }
    00952         }
    00953         DrawString(30, y, (sdb->str) + disabled, TC_FROMSTRING);
    00954         y += 11;
    00955       }
    00956     } break;
    00957
    00958     case WE_CLICK:
    00959       switch (e->we.click.widget) {
    00960         case PATCHSEL_OPTIONSPANEL: {
    00961           const PatchPage *page = &_patches_page[WP(w, def_d).data_1];
    00962           const SettingDesc *sd;
    00963           void *var;
    00964           int32 value;
    00965           int x, y;
    00966           byte btn;
    00967
    00968           y = e->we.click.pt.y - 46 - 1;
    00969           if (y < 0) return;
    00970
    00971           x = e->we.click.pt.x - 5;
    00972           if (x < 0) return;
    00973
    00974           btn = y / 11;
    00975           if (y % 11 > 9) return;
    00976           if (btn >= page->num) return;
    00977
    00978           sd = page->entries[btn].setting;
    00979
    00980           /* return if action is only active in network, or only settable by server */
    00981           if (!(sd->save.conv & SLF_NETWORK_NO) && _networking && !_network_server) return;
    00982           if ((sd->desc.flags & SGF_NETWORK_ONLY) && !_networking) return;
    00983           if ((sd->desc.flags & SGF_NO_NETWORK) && _networking) return;
    00984
    00985           var = GetVariableAddress(patches_ptr, &sd->save);
    00986           value = (int32)ReadValue(var, sd->save.conv);
    00987
    00988           /* clicked on the icon on the left side. Either scroller or bool on/off */
    00989           if (x < 21) {
    00990             const SettingDescBase *sdb = &sd->desc;
    00991             int32 oldvalue = value;
    00992
    00993             switch (sdb->cmd) {
    00994             case SDT_BOOLX: value ^= 1; break;
    00995             case SDT_NUMX: {
    00996               /* Add a dynamic step-size to the scroller. In a maximum of
    00997                * 50-steps you should be able to get from min to max,
    00998                * unless specified otherwise in the 'interval' variable
    00999                * of the current patch. */
    01000               uint32 step = (sdb->interval == 0) ? ((sdb->max - sdb->min) / 50) : sdb->interval;
    01001               if (step == 0) step = 1;
    01002
    01003               // don't allow too fast scrolling
    01004               if ((w->flags4 & WF_TIMEOUT_MASK) > 2 << WF_TIMEOUT_SHL) {
    01005                 _left_button_clicked = false;
    01006                 return;
    01007               }
    01008
    01009               /* Increase or decrease the value and clamp it to extremes */
    01010               if (x >= 10) {
    01011                 value += step;
    01012                 if (value > sdb->max) value = sdb->max;
    01013               } else {
    01014                 value -= step;
    01015                 if (value < sdb->min) value = (sdb->flags & SGF_0ISDISABLED) ? 0 : sdb->min;
    01016               }
    01017
    01018               /* Set up scroller timeout for numeric values */
    01019               if (value != oldvalue && !(sd->desc.flags & SGF_MULTISTRING)) {
    01020                 WP(w, def_d).data_2 = btn * 2 + 1 + ((x >= 10) ? 1 : 0);
    01021                 w->flags4 |= 5 << WF_TIMEOUT_SHL;
    01022                 _left_button_clicked = false;
    01023               }
    01024             } break;
    01025             default: NOT_REACHED();
    01026             }
    01027
    01028             if (value != oldvalue) {
    01029               SetPatchValue(page->entries[btn].index, patches_ptr, value);
    01030               SetWindowDirty(w);
    01031             }
    01032           } else {
    01033             /* only open editbox for types that its sensible for */
    01034             if (sd->desc.cmd != SDT_BOOLX && !(sd->desc.flags & SGF_MULTISTRING)) {
    01035               /* Show the correct currency-translated value */
    01036               if (sd->desc.flags & SGF_CURRENCY) value *= _currency->rate;
    01037
    01038               WP(w, def_d).data_3 = btn;
    01039               SetDParam(0, value);
    01040               ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_CONFIG_PATCHES_QUERY_CAPT, 10, 100, w, CS_NUMERAL);
    01041             }
    01042           }
    01043         } break;
    01044
    01045         case PATCHSEL_INTERFACE: case PATCHSEL_CONSTRUCTION: case PATCHSEL_VEHICLES:
    01046         case PATCHSEL_STATIONS:  case PATCHSEL_ECONOMY:      case PATCHSEL_COMPETITORS:
    01047           w->RaiseWidget(WP(w, def_d).data_1 + PATCHSEL_INTERFACE);
    01048           WP(w, def_d).data_1 = e->we.click.widget - PATCHSEL_INTERFACE;
    01049           w->LowerWidget(WP(w, def_d).data_1 + PATCHSEL_INTERFACE);
    01050           DeleteWindowById(WC_QUERY_STRING, 0);
    01051           SetWindowDirty(w);
    01052           break;
    01053       }
    01054       break;
    01055
    01056     case WE_TIMEOUT:
    01057       WP(w, def_d).data_2 = 0;
    01058       SetWindowDirty(w);
    01059       break;
    01060
    01061     case WE_ON_EDIT_TEXT:
    01062       if (e->we.edittext.str != NULL) {
    01063         const PatchEntry *pe = &_patches_page[WP(w, def_d).data_1].entries[WP(w,def_d).data_3];
    01064         const SettingDesc *sd = pe->setting;
    01065         int32 value = atoi(e->we.edittext.str);
    01066
    01067         /* Save the correct currency-translated value */
    01068         if (sd->desc.flags & SGF_CURRENCY) value /= _currency->rate;
    01069
    01070         SetPatchValue(pe->index, patches_ptr, value);
    01071         SetWindowDirty(w);
    01072       }
    01073       break;
    01074
    01075     case WE_DESTROY:
    01076       DeleteWindowById(WC_QUERY_STRING, 0);
    01077       break;
    01078   }
    01079 }
    01080
    01081 static const Widget _patches_selection_widgets[] = {
    01082 {   WWT_CLOSEBOX,   RESIZE_NONE,    10,     0,    10,     0,    13, STR_00C5,                        STR_018B_CLOSE_WINDOW},
    01083 {    WWT_CAPTION,   RESIZE_NONE,    10,    11,   369,     0,    13, STR_CONFIG_PATCHES_CAPTION,      STR_018C_WINDOW_TITLE_DRAG_THIS},
    01084 {      WWT_PANEL,   RESIZE_NONE,    10,     0,   369,    14,    41, 0x0,                             STR_NULL},
    01085 {      WWT_PANEL,   RESIZE_NONE,    10,     0,   369,    42,    50, 0x0,                             STR_NULL},
    01086
    01087 {    WWT_TEXTBTN,   RESIZE_NONE,     3,    10,    96,    16,    27, STR_CONFIG_PATCHES_GUI,          STR_NULL},
    01088 {    WWT_TEXTBTN,   RESIZE_NONE,     3,    97,   183,    16,    27, STR_CONFIG_PATCHES_CONSTRUCTION, STR_NULL},
    01089 {    WWT_TEXTBTN,   RESIZE_NONE,     3,   184,   270,    16,    27, STR_CONFIG_PATCHES_VEHICLES,     STR_NULL},
    01090 {    WWT_TEXTBTN,   RESIZE_NONE,     3,   271,   357,    16,    27, STR_CONFIG_PATCHES_STATIONS,     STR_NULL},
    01091 {    WWT_TEXTBTN,   RESIZE_NONE,     3,    10,    96,    28,    39, STR_CONFIG_PATCHES_ECONOMY,      STR_NULL},
    01092 {    WWT_TEXTBTN,   RESIZE_NONE,     3,    97,   183,    28,    39, STR_CONFIG_PATCHES_AI,           STR_NULL},
    01093 {   WIDGETS_END},
    01094 };
    01095
    01096 static const WindowDesc _patches_selection_desc = {
    01097   WDP_CENTER, WDP_CENTER, 370, 51, 370, 51,
    01098   WC_GAME_OPTIONS, WC_NONE,
    01099   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
    01100   _patches_selection_widgets,
    01101   PatchesSelectionWndProc,
    01102 };
    01103
    01104 void ShowPatchesSelection()
    01105 {
    01106   DeleteWindowById(WC_GAME_OPTIONS, 0);
    01107   AllocateWindowDesc(&_patches_selection_desc);
    01108 }
    01109
    01110
    01120 void DrawArrowButtons(int x, int y, int ctab, byte state, bool clickable_left, bool clickable_right)
    01121 {
    01122   int color = (1 << PALETTE_MODIFIER_GREYOUT) | _colour_gradient[COLOUR_YELLOW][2];
    01123
    01124   DrawFrameRect(x,      y + 1, x +  9, y + 9, ctab, (state == 1) ? FR_LOWERED : FR_NONE);
    01125   DrawFrameRect(x + 10, y + 1, x + 19, y + 9, ctab, (state == 2) ? FR_LOWERED : FR_NONE);
    01126   DrawStringCentered(x +  5, y + 1, STR_6819, TC_FROMSTRING); // [<]
    01127   DrawStringCentered(x + 15, y + 1, STR_681A, TC_FROMSTRING); // [>]
    01128
    01129   /* Grey out the buttons that aren't clickable */
    01130   if (!clickable_left)
    01131     GfxFillRect(x +  1, y + 1, x +  1 + 8, y + 8, color);
    01132   if (!clickable_right)
    01133     GfxFillRect(x + 11, y + 1, x + 11 + 8, y + 8, color);
    01134 }
    01135
    01139 enum CustomCurrenciesWidgets {
    01140   CUSTCURR_EXCHANGERATE = 0,
    01141   CUSTCURR_SEPARATOR,
    01142   CUSTCURR_PREFIX,
    01143   CUSTCURR_SUFFIX,
    01144   CUSTCURR_TO_EURO,
    01145 };
    01146
    01147 static char _str_separator[2];
    01148
    01149 static void CustCurrencyWndProc(Window *w, WindowEvent *e)
    01150 {
    01151   switch (e->event) {
    01152     case WE_PAINT: {
    01153       int x;
    01154       int y = 20;
    01155       int clk = WP(w, def_d).data_1;
    01156       DrawWindowWidgets(w);
    01157
    01158       /* exchange rate */
    01159       DrawArrowButtons(10, y, 3, GB(clk, 0, 2), true, true);
    01160       SetDParam(0, 1);
    01161       SetDParam(1, 1);
    01162       DrawString(35, y + 1, STR_CURRENCY_EXCHANGE_RATE, TC_FROMSTRING);
    01163       y += 12;
    01164
    01165       /* separator */
    01166       DrawFrameRect(10, y + 1, 29, y + 9, 0, GB(clk, 2, 2) ? FR_LOWERED : FR_NONE);
    01167       x = DrawString(35, y + 1, STR_CURRENCY_SEPARATOR, TC_FROMSTRING);
    01168       DoDrawString(_str_separator, x + 4, y + 1, TC_ORANGE);
    01169       y += 12;
    01170
    01171       /* prefix */
    01172       DrawFrameRect(10, y + 1, 29, y + 9, 0, GB(clk, 4, 2) ? FR_LOWERED : FR_NONE);
    01173       x = DrawString(35, y + 1, STR_CURRENCY_PREFIX, TC_FROMSTRING);
    01174       DoDrawString(_custom_currency.prefix, x + 4, y + 1, TC_ORANGE);
    01175       y += 12;
    01176
    01177       /* suffix */
    01178       DrawFrameRect(10, y + 1, 29, y + 9, 0, GB(clk, 6, 2) ? FR_LOWERED : FR_NONE);
    01179       x = DrawString(35, y + 1, STR_CURRENCY_SUFFIX, TC_FROMSTRING);
    01180       DoDrawString(_custom_currency.suffix, x + 4, y + 1, TC_ORANGE);
    01181       y += 12;
    01182
    01183       /* switch to euro */
    01184       DrawArrowButtons(10, y, 3, GB(clk, 8, 2), true, true);
    01185       SetDParam(0, _custom_currency.to_euro);
    01186       DrawString(35, y + 1, (_custom_currency.to_euro != CF_NOEURO) ? STR_CURRENCY_SWITCH_TO_EURO : STR_CURRENCY_SWITCH_TO_EURO_NEVER, TC_FROMSTRING);
    01187       y += 12;
    01188
    01189       /* Preview */
    01190       y += 12;
    01191       SetDParam(0, 10000);
    01192       DrawString(35, y + 1, STR_CURRENCY_PREVIEW, TC_FROMSTRING);
    01193     } break;
    01194
    01195     case WE_CLICK: {
    01196       int line = (e->we.click.pt.y - 20) / 12;
    01197       int len = 0;
    01198       int x = e->we.click.pt.x;
    01199       StringID str = 0;
    01200       CharSetFilter afilter = CS_ALPHANUMERAL;
    01201
    01202       switch (line) {
    01203         case CUSTCURR_EXCHANGERATE:
    01204           if (IsInsideMM(x, 10, 30)) { // clicked buttons
    01205             if (x < 20) {
    01206               if (_custom_currency.rate > 1) _custom_currency.rate--;
    01207               WP(w, def_d).data_1 = 1 << (line * 2 + 0);
    01208             } else {
    01209               if (_custom_currency.rate < 5000) _custom_currency.rate++;
    01210               WP(w, def_d).data_1 = 1 << (line * 2 + 1);
    01211             }
    01212           } else { // enter text
    01213             SetDParam(0, _custom_currency.rate);
    01214             str = STR_CONFIG_PATCHES_INT32;
    01215             len = 4;
    01216             afilter = CS_NUMERAL;
    01217           }
    01218           break;
    01219
    01220         case CUSTCURR_SEPARATOR:
    01221           if (IsInsideMM(x, 10, 30)) { // clicked button
    01222             WP(w, def_d).data_1 = 1 << (line * 2 + 1);
    01223           }
    01224           str = BindCString(_str_separator);
    01225           len = 1;
    01226           break;
    01227
    01228         case CUSTCURR_PREFIX:
    01229           if (IsInsideMM(x, 10, 30)) { // clicked button
    01230             WP(w, def_d).data_1 = 1 << (line * 2 + 1);
    01231           }
    01232           str = BindCString(_custom_currency.prefix);
    01233           len = 12;
    01234           break;
    01235
    01236         case CUSTCURR_SUFFIX:
    01237           if (IsInsideMM(x, 10, 30)) { // clicked button
    01238             WP(w, def_d).data_1 = 1 << (line * 2 + 1);
    01239           }
    01240           str = BindCString(_custom_currency.suffix);
    01241           len = 12;
    01242           break;
    01243
    01244         case CUSTCURR_TO_EURO:
    01245           if (IsInsideMM(x, 10, 30)) { // clicked buttons
    01246             if (x < 20) {
    01247               _custom_currency.to_euro = (_custom_currency.to_euro <= 2000) ?
    01248                 CF_NOEURO : _custom_currency.to_euro - 1;
    01249               WP(w, def_d).data_1 = 1 << (line * 2 + 0);
    01250             } else {
    01251               _custom_currency.to_euro =
    01252                 Clamp(_custom_currency.to_euro + 1, 2000, MAX_YEAR);
    01253               WP(w, def_d).data_1 = 1 << (line * 2 + 1);
    01254             }
    01255           } else { // enter text
    01256             SetDParam(0, _custom_currency.to_euro);
    01257             str = STR_CONFIG_PATCHES_INT32;
    01258             len = 4;
    01259             afilter = CS_NUMERAL;
    01260           }
    01261           break;
    01262       }
    01263
    01264       if (len != 0) {
    01265         WP(w, def_d).data_2 = line;
    01266         ShowQueryString(str, STR_CURRENCY_CHANGE_PARAMETER, len + 1, 250, w, afilter);
    01267       }
    01268
    01269       w->flags4 |= 5 << WF_TIMEOUT_SHL;
    01270       SetWindowDirty(w);
    01271     } break;
    01272
    01273     case WE_ON_EDIT_TEXT: {
    01274       const char *b = e->we.edittext.str;
    01275
    01276       switch (WP(w, def_d).data_2) {
    01277         case CUSTCURR_EXCHANGERATE:
    01278           _custom_currency.rate = Clamp(atoi(b), 1, 5000);
    01279           break;
    01280
    01281         case CUSTCURR_SEPARATOR: /* Thousands seperator */
    01282           _custom_currency.separator = (b[0] == '\0') ? ' ' : b[0];
    01283           ttd_strlcpy(_str_separator, b, lengthof(_str_separator));
    01284           break;
    01285
    01286         case CUSTCURR_PREFIX:
    01287           ttd_strlcpy(_custom_currency.prefix, b, lengthof(_custom_currency.prefix));
    01288           break;
    01289
    01290         case CUSTCURR_SUFFIX:
    01291           ttd_strlcpy(_custom_currency.suffix, b, lengthof(_custom_currency.suffix));
    01292           break;
    01293
    01294         case CUSTCURR_TO_EURO: { /* Year to switch to euro */
    01295           int val = atoi(b);
    01296
    01297           _custom_currency.to_euro = (val < 2000 ? CF_NOEURO : min(val, MAX_YEAR));
    01298           break;
    01299         }
    01300       }
    01301       MarkWholeScreenDirty();
    01302     } break;
    01303
    01304     case WE_TIMEOUT:
    01305       WP(w, def_d).data_1 = 0;
    01306       SetWindowDirty(w);
    01307       break;
    01308
    01309     case WE_DESTROY:
    01310       DeleteWindowById(WC_QUERY_STRING, 0);
    01311       MarkWholeScreenDirty();
    01312       break;
    01313   }
    01314 }
    01315
    01316 static const Widget _cust_currency_widgets[] = {
    01317 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,            STR_018B_CLOSE_WINDOW},
    01318 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   229,     0,    13, STR_CURRENCY_WINDOW, STR_018C_WINDOW_TITLE_DRAG_THIS},
    01319 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   229,    14,   119, 0x0,                 STR_NULL},
    01320 {   WIDGETS_END},
    01321 };
    01322
    01323 static const WindowDesc _cust_currency_desc = {
    01324   WDP_CENTER, WDP_CENTER, 230, 120, 230, 120,
    01325   WC_CUSTOM_CURRENCY, WC_NONE,
    01326   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
    01327   _cust_currency_widgets,
    01328   CustCurrencyWndProc,
    01329 };
    01330
    01331 static void ShowCustCurrency()
    01332 {
    01333   _str_separator[0] = _custom_currency.separator;
    01334   _str_separator[1] = '\0';
    01335
    01336   DeleteWindowById(WC_CUSTOM_CURRENCY, 0);
    01337   AllocateWindowDesc(&_cust_currency_desc);
    01338 }