OpenTTD-patches/settings_gui.c

1528 lines
53 KiB
C

/* $Id$ */
#include "stdafx.h"
#include "openttd.h"
#include "currency.h"
#include "functions.h"
#include "string.h"
#include "strings.h" // XXX GetCurrentCurrencyRate()
#include "table/sprites.h"
#include "table/strings.h"
#include "window.h"
#include "gui.h"
#include "gfx.h"
#include "command.h"
#include "engine.h"
#include "screenshot.h"
#include "newgrf.h"
#include "network.h"
#include "console.h"
#include "town.h"
#include "variables.h"
static uint32 _difficulty_click_a;
static uint32 _difficulty_click_b;
static byte _difficulty_timeout;
static const StringID _distances_dropdown[] = {
STR_0139_IMPERIAL_MILES,
STR_013A_METRIC_KILOMETERS,
INVALID_STRING_ID
};
static const StringID _driveside_dropdown[] = {
STR_02E9_DRIVE_ON_LEFT,
STR_02EA_DRIVE_ON_RIGHT,
INVALID_STRING_ID
};
static const StringID _autosave_dropdown[] = {
STR_02F7_OFF,
STR_AUTOSAVE_1_MONTH,
STR_02F8_EVERY_3_MONTHS,
STR_02F9_EVERY_6_MONTHS,
STR_02FA_EVERY_12_MONTHS,
INVALID_STRING_ID,
};
static const StringID _designnames_dropdown[] = {
STR_02BE_DEFAULT,
STR_02BF_CUSTOM,
INVALID_STRING_ID
};
static StringID *BuildDynamicDropdown(StringID base, int num)
{
static StringID buf[32 + 1];
StringID *p = buf;
while (--num>=0) *p++ = base++;
*p = INVALID_STRING_ID;
return buf;
}
static int GetCurRes(void)
{
int i;
for(i = 0; i != _num_resolutions; i++)
if (_resolutions[i][0] == _screen.width &&
_resolutions[i][1] == _screen.height)
break;
return i;
}
static inline bool RoadVehiclesAreBuilt(void)
{
const Vehicle *v;
FOR_ALL_VEHICLES(v) {
if (v->type == VEH_Road) return true;
}
return false;
}
static void GameOptionsWndProc(Window *w, WindowEvent *e)
{
switch (e->event) {
case WE_PAINT: {
int i;
StringID str = STR_02BE_DEFAULT;
w->disabled_state = (_vehicle_design_names & 1) ? (++str, 0) : (1 << 21);
SetDParam(0, str);
SetDParam(1, _currency_string_list[_opt_ptr->currency]);
SetDParam(2, _opt_ptr->kilometers + STR_0139_IMPERIAL_MILES);
SetDParam(3, STR_02E9_DRIVE_ON_LEFT + _opt_ptr->road_side);
SetDParam(4, STR_TOWNNAME_ORIGINAL_ENGLISH + _opt_ptr->town_name);
SetDParam(5, _autosave_dropdown[_opt_ptr->autosave]);
SetDParam(6, SPECSTR_LANGUAGE_START + _dynlang.curr);
i = GetCurRes();
SetDParam(7, i == _num_resolutions ? STR_RES_OTHER : SPECSTR_RESOLUTION_START + i);
SetDParam(8, SPECSTR_SCREENSHOT_START + _cur_screenshot_format);
(_fullscreen) ? SETBIT(w->click_state, 28) : CLRBIT(w->click_state, 28); // fullscreen button
DrawWindowWidgets(w);
DrawString(20, 175, STR_OPTIONS_FULLSCREEN, 0); // fullscreen
} break;
case WE_CLICK:
switch (e->click.widget) {
case 4: case 5: /* Setup currencies dropdown */
ShowDropDownMenu(w, _currency_string_list, _opt_ptr->currency, 5, _game_mode == GM_MENU ? 0 : ~GetMaskOfAllowedCurrencies(), 0);
return;
case 7: case 8: /* Setup distance unit dropdown */
ShowDropDownMenu(w, _distances_dropdown, _opt_ptr->kilometers, 8, 0, 0);
return;
case 10: case 11: { /* Setup road-side dropdown */
int i = 0;
/* You can only change the drive side if you are in the menu or ingame with
* no vehicles present. In a networking game only the server can change it */
if ((_game_mode != GM_MENU && RoadVehiclesAreBuilt()) || (_networking && !_network_server))
i = (-1) ^ (1 << _opt_ptr->road_side); // disable the other value
ShowDropDownMenu(w, _driveside_dropdown, _opt_ptr->road_side, 11, i, 0);
} return;
case 13: case 14: { /* Setup townname dropdown */
int i = _opt_ptr->town_name;
ShowDropDownMenu(w, BuildDynamicDropdown(STR_TOWNNAME_ORIGINAL_ENGLISH, SPECSTR_TOWNNAME_LAST - SPECSTR_TOWNNAME_START + 1), i, 14, (_game_mode == GM_MENU) ? 0 : (-1) ^ (1 << i), 0);
return;
}
case 16: case 17: /* Setup autosave dropdown */
ShowDropDownMenu(w, _autosave_dropdown, _opt_ptr->autosave, 17, 0, 0);
return;
case 19: case 20: /* Setup customized vehicle-names dropdown */
ShowDropDownMenu(w, _designnames_dropdown, (_vehicle_design_names & 1) ? 1 : 0, 20, (_vehicle_design_names & 2) ? 0 : 2, 0);
return;
case 21: /* Save customized vehicle-names to disk */
return;
case 23: case 24: /* Setup interface language dropdown */
ShowDropDownMenu(w, _dynlang.dropdown, _dynlang.curr, 24, 0, 0);
return;
case 26: case 27: /* Setup resolution dropdown */
ShowDropDownMenu(w, BuildDynamicDropdown(SPECSTR_RESOLUTION_START, _num_resolutions), GetCurRes(), 27, 0, 0);
return;
case 28: /* Click fullscreen on/off */
(_fullscreen) ? CLRBIT(w->click_state, 28) : SETBIT(w->click_state, 28);
ToggleFullScreen(!_fullscreen); // toggle full-screen on/off
SetWindowDirty(w);
return;
case 30: case 31: /* Setup screenshot format dropdown */
ShowDropDownMenu(w, BuildDynamicDropdown(SPECSTR_SCREENSHOT_START, _num_screenshot_formats), _cur_screenshot_format, 31, 0, 0);
return;
}
break;
case WE_DROPDOWN_SELECT:
switch (e->dropdown.button) {
case 20: /* Vehicle design names */
if (e->dropdown.index == 0) {
DeleteCustomEngineNames();
MarkWholeScreenDirty();
} else if (!(_vehicle_design_names & 1)) {
LoadCustomEngineNames();
MarkWholeScreenDirty();
}
break;
case 5: /* Currency */
if (e->dropdown.index == 23)
ShowCustCurrency();
_opt_ptr->currency = e->dropdown.index;
MarkWholeScreenDirty();
break;
case 8: /* Distance units */
_opt_ptr->kilometers = e->dropdown.index;
MarkWholeScreenDirty();
break;
case 11: /* Road side */
if (_opt_ptr->road_side != e->dropdown.index) { // only change if setting changed
DoCommandP(0, e->dropdown.index, 0, NULL, CMD_SET_ROAD_DRIVE_SIDE | CMD_MSG(STR_00B4_CAN_T_DO_THIS));
MarkWholeScreenDirty();
}
break;
case 14: /* Town names */
if (_game_mode == GM_MENU) {
_opt_ptr->town_name = e->dropdown.index;
InvalidateWindow(WC_GAME_OPTIONS, 0);
}
break;
case 17: /* Autosave options */
_opt_ptr->autosave = e->dropdown.index;
SetWindowDirty(w);
break;
case 24: /* Change interface language */
ReadLanguagePack(e->dropdown.index);
MarkWholeScreenDirty();
break;
case 27: /* Change resolution */
if (e->dropdown.index < _num_resolutions && ChangeResInGame(_resolutions[e->dropdown.index][0],_resolutions[e->dropdown.index][1]))
SetWindowDirty(w);
break;
case 31: /* Change screenshot format */
SetScreenshotFormat(e->dropdown.index);
SetWindowDirty(w);
break;
}
break;
case WE_DESTROY:
DeleteWindowById(WC_CUSTOM_CURRENCY, 0);
break;
}
}
/** Change the side of the road vehicles drive on (server only).
* @param x,y unused
* @param p1 the side of the road; 0 = left side and 1 = right side
* @param p2 unused
*/
int32 CmdSetRoadDriveSide(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
/* Check boundaries and you can only change this if NO vehicles have been built yet,
* except in the intro-menu where of course it's always possible to do so. */
if (p1 > 1 || (_game_mode != GM_MENU && RoadVehiclesAreBuilt())) return CMD_ERROR;
if (flags & DC_EXEC) {
_opt_ptr->road_side = p1;
InvalidateWindow(WC_GAME_OPTIONS,0);
}
return 0;
}
static const Widget _game_options_widgets[] = {
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW},
{ WWT_CAPTION, RESIZE_NONE, 14, 11, 369, 0, 13, STR_00B1_GAME_OPTIONS, STR_018C_WINDOW_TITLE_DRAG_THIS},
{ WWT_PANEL, RESIZE_NONE, 14, 0, 369, 14, 238, 0x0, STR_NULL},
{ WWT_FRAME, RESIZE_NONE, 14, 10, 179, 20, 55, STR_02E0_CURRENCY_UNITS, STR_NULL},
{ WWT_6, RESIZE_NONE, 14, 20, 169, 34, 45, STR_02E1, STR_02E2_CURRENCY_UNITS_SELECTION},
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 158, 168, 35, 44, STR_0225, STR_02E2_CURRENCY_UNITS_SELECTION},
{ WWT_FRAME, RESIZE_NONE, 14, 190, 359, 20, 55, STR_02E3_DISTANCE_UNITS, STR_NULL},
{ WWT_6, RESIZE_NONE, 14, 200, 349, 34, 45, STR_02E4, STR_02E5_DISTANCE_UNITS_SELECTION},
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 338, 348, 35, 44, STR_0225, STR_02E5_DISTANCE_UNITS_SELECTION},
{ WWT_FRAME, RESIZE_NONE, 14, 10, 179, 62, 97, STR_02E6_ROAD_VEHICLES, STR_NULL},
{ WWT_6, RESIZE_NONE, 14, 20, 169, 76, 87, STR_02E7, STR_02E8_SELECT_SIDE_OF_ROAD_FOR},
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 158, 168, 77, 86, STR_0225, STR_02E8_SELECT_SIDE_OF_ROAD_FOR},
{ WWT_FRAME, RESIZE_NONE, 14, 190, 359, 62, 97, STR_02EB_TOWN_NAMES, STR_NULL},
{ WWT_6, RESIZE_NONE, 14, 200, 349, 76, 87, STR_02EC, STR_02ED_SELECT_STYLE_OF_TOWN_NAMES},
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 338, 348, 77, 86, STR_0225, STR_02ED_SELECT_STYLE_OF_TOWN_NAMES},
{ WWT_FRAME, RESIZE_NONE, 14, 10, 179, 104, 139, STR_02F4_AUTOSAVE, STR_NULL},
{ WWT_6, RESIZE_NONE, 14, 20, 169, 118, 129, STR_02F5, STR_02F6_SELECT_INTERVAL_BETWEEN},
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 158, 168, 119, 128, STR_0225, STR_02F6_SELECT_INTERVAL_BETWEEN},
{ WWT_FRAME, RESIZE_NONE, 14, 10, 359, 194, 228, STR_02BC_VEHICLE_DESIGN_NAMES, STR_NULL},
{ WWT_6, RESIZE_NONE, 14, 20, 119, 207, 218, STR_02BD, STR_02C1_VEHICLE_DESIGN_NAMES_SELECTION},
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 108, 118, 208, 217, STR_0225, STR_02C1_VEHICLE_DESIGN_NAMES_SELECTION},
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 130, 349, 207, 218, STR_02C0_SAVE_CUSTOM_NAMES, STR_02C2_SAVE_CUSTOMIZED_VEHICLE},
{ WWT_FRAME, RESIZE_NONE, 14, 190, 359, 104, 139, STR_OPTIONS_LANG, STR_NULL},
{ WWT_6, RESIZE_NONE, 14, 200, 349, 118, 129, STR_OPTIONS_LANG_CBO, STR_OPTIONS_LANG_TIP},
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 338, 348, 119, 128, STR_0225, STR_OPTIONS_LANG_TIP},
{ WWT_FRAME, RESIZE_NONE, 14, 10, 179, 146, 190, STR_OPTIONS_RES, STR_NULL},
{ WWT_6, RESIZE_NONE, 14, 20, 169, 160, 171, STR_OPTIONS_RES_CBO, STR_OPTIONS_RES_TIP},
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 158, 168, 161, 170, STR_0225, STR_OPTIONS_RES_TIP},
{ WWT_TEXTBTN, RESIZE_NONE, 14, 149, 169, 176, 184, STR_EMPTY, STR_OPTIONS_FULLSCREEN_TIP},
{ WWT_FRAME, RESIZE_NONE, 14, 190, 359, 146, 190, STR_OPTIONS_SCREENSHOT_FORMAT, STR_NULL},
{ WWT_6, RESIZE_NONE, 14, 200, 349, 160, 171, STR_OPTIONS_SCREENSHOT_FORMAT_CBO, STR_OPTIONS_SCREENSHOT_FORMAT_TIP},
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 338, 348, 161, 170, STR_0225, STR_OPTIONS_SCREENSHOT_FORMAT_TIP},
{ WIDGETS_END},
};
static const WindowDesc _game_options_desc = {
WDP_CENTER, WDP_CENTER, 370, 239,
WC_GAME_OPTIONS,0,
WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
_game_options_widgets,
GameOptionsWndProc
};
void ShowGameOptions(void)
{
DeleteWindowById(WC_GAME_OPTIONS, 0);
AllocateWindowDesc(&_game_options_desc);
}
typedef struct {
int16 min;
int16 max;
int16 step;
StringID str;
} GameSettingData;
static const GameSettingData _game_setting_info[] = {
{ 0, 7, 1, STR_NULL},
{ 0, 3, 1, STR_6830_IMMEDIATE},
{ 0, 2, 1, STR_6816_LOW},
{ 0, 3, 1, STR_26816_NONE},
{100, 500, 50, STR_NULL},
{ 2, 4, 1, STR_NULL},
{ 0, 2, 1, STR_6820_LOW},
{ 0, 4, 1, STR_681B_VERY_SLOW},
{ 0, 2, 1, STR_6820_LOW},
{ 0, 2, 1, STR_6823_NONE},
{ 0, 3, 1, STR_6826_X1_5},
{ 0, 2, 1, STR_6820_LOW},
{ 0, 3, 1, STR_682A_VERY_FLAT},
{ 0, 3, 1, STR_VERY_LOW},
{ 0, 1, 1, STR_682E_STEADY},
{ 0, 1, 1, STR_6834_AT_END_OF_LINE_AND_AT_STATIONS},
{ 0, 1, 1, STR_6836_OFF},
{ 0, 2, 1, STR_6839_PERMISSIVE},
};
static inline bool GetBitAndShift(uint32 *b)
{
uint32 x = *b;
*b >>= 1;
return HASBIT(x, 0);
}
/*
A: competitors
B: start time in months / 3
C: town count (2 = high, 0 = low)
D: industry count (3 = high, 0 = none)
E: inital loan / 1000 (in GBP)
F: interest rate
G: running costs (0 = low, 2 = high)
H: construction speed of competitors (0 = very slow, 4 = very fast)
I: intelligence (0-2)
J: breakdowns(0 = off, 2 = normal)
K: subsidy multiplier (0 = 1.5, 3 = 4.0)
L: construction cost (0-2)
M: terrain type (0 = very flat, 3 = mountainous)
N: amount of water (0 = very low, 3 = high)
O: economy (0 = steady, 1 = fluctuating)
P: Train reversing (0 = end of line + stations, 1 = end of line)
Q: disasters
R: area restructuring (0 = permissive, 2 = hostile)
*/
static const int16 _default_game_diff[3][GAME_DIFFICULTY_NUM] = { /*
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R*/
{2, 2, 1, 3, 300, 2, 0, 2, 0, 1, 2, 0, 1, 0, 0, 0, 0, 0}, //easy
{4, 1, 1, 2, 150, 3, 1, 3, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1}, //medium
{7, 0, 2, 2, 100, 4, 1, 3, 2, 2, 0, 2, 3, 2, 1, 1, 1, 2}, //hard
};
void SetDifficultyLevel(int mode, GameOptions *gm_opt)
{
int i;
assert(mode <= 3);
gm_opt->diff_level = mode;
if (mode != 3) { // not custom
for (i = 0; i != GAME_DIFFICULTY_NUM; i++)
((int*)&gm_opt->diff)[i] = _default_game_diff[mode][i];
}
}
extern void StartupEconomy(void);
enum {
GAMEDIFF_WND_TOP_OFFSET = 45,
GAMEDIFF_WND_ROWSIZE = 9
};
// Temporary holding place of values in the difficulty window until 'Save' is clicked
static GameOptions _opt_mod_temp;
// 0x383E = (1 << 13) | (1 << 12) | (1 << 11) | (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1)
#define DIFF_INGAME_DISABLED_BUTTONS 0x383E
static void GameDifficultyWndProc(Window *w, WindowEvent *e)
{
switch (e->event) {
case WE_CREATE: /* Setup disabled buttons when creating window */
// disable all other difficulty buttons during gameplay except for 'custom'
w->disabled_state = (_game_mode != GM_NORMAL) ? 0 : (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
if (_game_mode == GM_EDITOR) SETBIT(w->disabled_state, 7);
if (_networking) {
SETBIT(w->disabled_state, 7); // disable highscore chart in multiplayer
if (!_network_server)
SETBIT(w->disabled_state, 10); // Disable save-button in multiplayer (and if client)
}
break;
case WE_PAINT: {
uint32 click_a, click_b, disabled;
int i;
int y, value;
w->click_state = (1 << 3) << _opt_mod_temp.diff_level; // have current difficulty button clicked
DrawWindowWidgets(w);
click_a = _difficulty_click_a;
click_b = _difficulty_click_b;
/* XXX - Disabled buttons in normal gameplay. Bitshifted for each button to see if
* that bit is set. If it is set, the button is disabled */
disabled = (_game_mode == GM_NORMAL) ? DIFF_INGAME_DISABLED_BUTTONS : 0;
y = GAMEDIFF_WND_TOP_OFFSET;
for (i = 0; i != GAME_DIFFICULTY_NUM; i++) {
DrawFrameRect( 5, y, 5 + 8, y + 8, 3, GetBitAndShift(&click_a) ? (1 << 5) : 0);
DrawFrameRect(15, y, 15 + 8, y + 8, 3, GetBitAndShift(&click_b) ? (1 << 5) : 0);
if (GetBitAndShift(&disabled) || (_networking && !_network_server)) {
int color = PALETTE_MODIFIER_GREYOUT | _color_list[3].unk2;
GfxFillRect( 6, y + 1, 6 + 8, y + 8, color);
GfxFillRect(16, y + 1, 16 + 8, y + 8, color);
}
DrawStringCentered(10, y, STR_6819, 0);
DrawStringCentered(20, y, STR_681A, 0);
value = _game_setting_info[i].str + ((int*)&_opt_mod_temp.diff)[i];
if (i == 4) value *= 1000; // XXX - handle currency option
SetDParam(0, value);
DrawString(30, y, STR_6805_MAXIMUM_NO_COMPETITORS + i, 0);
y += GAMEDIFF_WND_ROWSIZE + 2; // space items apart a bit
}
} break;
case WE_CLICK:
switch (e->click.widget) {
case 8: { /* Difficulty settings widget, decode click */
const GameSettingData *info;
int x, y;
uint btn, dis;
int val;
// Don't allow clients to make any changes
if (_networking && !_network_server)
return;
x = e->click.pt.x - 5;
if (!IS_INT_INSIDE(x, 0, 21)) // Button area
return;
y = e->click.pt.y - GAMEDIFF_WND_TOP_OFFSET;
if (y < 0)
return;
// Get button from Y coord.
btn = y / (GAMEDIFF_WND_ROWSIZE + 2);
if (btn >= GAME_DIFFICULTY_NUM || y % (GAMEDIFF_WND_ROWSIZE + 2) >= 9)
return;
// Clicked disabled button?
dis = (_game_mode == GM_NORMAL) ? DIFF_INGAME_DISABLED_BUTTONS : 0;
if (HASBIT(dis, btn))
return;
_difficulty_timeout = 5;
val = ((int*)&_opt_mod_temp.diff)[btn];
info = &_game_setting_info[btn]; // get information about the difficulty setting
if (x >= 10) {
// Increase button clicked
val = min(val + info->step, info->max);
SETBIT(_difficulty_click_b, btn);
} else {
// Decrease button clicked
val = max(val - info->step, info->min);
SETBIT(_difficulty_click_a, btn);
}
// save value in temporary variable
((int*)&_opt_mod_temp.diff)[btn] = val;
SetDifficultyLevel(3, &_opt_mod_temp); // set difficulty level to custom
SetWindowDirty(w);
} break;
case 3: case 4: case 5: case 6: /* Easy / Medium / Hard / Custom */
// temporarily change difficulty level
SetDifficultyLevel(e->click.widget - 3, &_opt_mod_temp);
SetWindowDirty(w);
break;
case 7: /* Highscore Table */
ShowHighscoreTable(_opt_mod_temp.diff_level, -1);
break;
case 10: { /* Save button - save changes */
int btn, val;
for (btn = 0; btn != GAME_DIFFICULTY_NUM; btn++) {
val = ((int*)&_opt_mod_temp.diff)[btn];
// if setting has changed, change it
if (val != ((int*)&_opt_ptr->diff)[btn])
DoCommandP(0, btn, val, NULL, CMD_CHANGE_DIFFICULTY_LEVEL);
}
DoCommandP(0, -1, _opt_mod_temp.diff_level, NULL, CMD_CHANGE_DIFFICULTY_LEVEL);
DeleteWindow(w);
// If we are in the editor, we should reload the economy.
// This way when you load a game, the max loan and interest rate
// are loaded correctly.
if (_game_mode == GM_EDITOR)
StartupEconomy();
break;
}
case 11: /* Cancel button - close window, abandon changes */
DeleteWindow(w);
break;
} break;
case WE_MOUSELOOP: /* Handle the visual 'clicking' of the buttons */
if (_difficulty_timeout != 0 && !--_difficulty_timeout) {
_difficulty_click_a = 0;
_difficulty_click_b = 0;
SetWindowDirty(w);
}
break;
}
}
#undef DIFF_INGAME_DISABLED_BUTTONS
static const Widget _game_difficulty_widgets[] = {
{ WWT_CLOSEBOX, RESIZE_NONE, 10, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW},
{ WWT_CAPTION, RESIZE_NONE, 10, 11, 369, 0, 13, STR_6800_DIFFICULTY_LEVEL, STR_018C_WINDOW_TITLE_DRAG_THIS},
{ WWT_PANEL, RESIZE_NONE, 10, 0, 369, 14, 29, 0x0, STR_NULL},
{ WWT_PUSHTXTBTN, RESIZE_NONE, 3, 10, 96, 16, 27, STR_6801_EASY, STR_NULL},
{ WWT_PUSHTXTBTN, RESIZE_NONE, 3, 97, 183, 16, 27, STR_6802_MEDIUM, STR_NULL},
{ WWT_PUSHTXTBTN, RESIZE_NONE, 3, 184, 270, 16, 27, STR_6803_HARD, STR_NULL},
{ WWT_PUSHTXTBTN, RESIZE_NONE, 3, 271, 357, 16, 27, STR_6804_CUSTOM, STR_NULL},
{ WWT_CLOSEBOX, RESIZE_NONE, 10, 0, 369, 30, 41, STR_6838_SHOW_HI_SCORE_CHART,STR_NULL},
{ WWT_PANEL, RESIZE_NONE, 10, 0, 369, 42, 262, 0x0, STR_NULL},
{ WWT_PANEL, RESIZE_NONE, 10, 0, 369, 263, 278, 0x0, STR_NULL},
{ WWT_PUSHTXTBTN, RESIZE_NONE, 3, 105, 185, 265, 276, STR_OPTIONS_SAVE_CHANGES, STR_NULL},
{ WWT_PUSHTXTBTN, RESIZE_NONE, 3, 186, 266, 265, 276, STR_012E_CANCEL, STR_NULL},
{ WIDGETS_END},
};
static const WindowDesc _game_difficulty_desc = {
WDP_CENTER, WDP_CENTER, 370, 279,
WC_GAME_OPTIONS,0,
WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
_game_difficulty_widgets,
GameDifficultyWndProc
};
void ShowGameDifficulty(void)
{
DeleteWindowById(WC_GAME_OPTIONS, 0);
/* Copy current settings (ingame or in intro) to temporary holding place
* change that when setting stuff, copy back on clicking 'OK' */
memcpy(&_opt_mod_temp, _opt_ptr, sizeof(GameOptions));
AllocateWindowDesc(&_game_difficulty_desc);
}
// virtual PositionMainToolbar function, calls the right one.
static int32 v_PositionMainToolbar(int32 p1)
{
if (_game_mode != GM_MENU)
PositionMainToolbar(NULL);
return 0;
}
static int32 AiNew_PatchActive_Warning(int32 p1)
{
if (p1 == 1)
ShowErrorMessage(-1, TEMP_AI_ACTIVATED, 0, 0);
return 0;
}
static int32 PopulationInLabelActive(int32 p1)
{
Town *t;
FOR_ALL_TOWNS(t) {
if (t->xy) {
UpdateTownVirtCoord(t);
}
}
return 0;
}
static int32 InvisibleTreesActive(int32 p1)
{
MarkWholeScreenDirty();
return 0;
}
static int32 InValidateDetailsWindow(int32 p1)
{
InvalidateWindowClasses(WC_VEHICLE_DETAILS);
return 0;
}
static int32 InvalidateStationBuildWindow(int32 p1)
{
InvalidateWindow(WC_BUILD_STATION, 0);
return 0;
}
/* Check service intervals of vehicles, p1 is value of % or day based servicing */
static int32 CheckInterval(int32 p1)
{
bool warning;
if (p1) {
warning = ( (IS_INT_INSIDE(_patches.servint_trains, 5, 90+1) || _patches.servint_trains == 0) &&
(IS_INT_INSIDE(_patches.servint_roadveh, 5, 90+1) || _patches.servint_roadveh == 0) &&
(IS_INT_INSIDE(_patches.servint_aircraft, 5, 90+1) || _patches.servint_aircraft == 0) &&
(IS_INT_INSIDE(_patches.servint_ships, 5, 90+1) || _patches.servint_ships == 0) );
} else {
warning = ( (IS_INT_INSIDE(_patches.servint_trains, 30, 800+1) || _patches.servint_trains == 0) &&
(IS_INT_INSIDE(_patches.servint_roadveh, 30, 800+1) || _patches.servint_roadveh == 0) &&
(IS_INT_INSIDE(_patches.servint_aircraft, 30, 800+1) || _patches.servint_aircraft == 0) &&
(IS_INT_INSIDE(_patches.servint_ships, 30, 800+1) || _patches.servint_ships == 0) );
}
if (!warning)
ShowErrorMessage(-1, STR_CONFIG_PATCHES_SERVICE_INTERVAL_INCOMPATIBLE, 0, 0);
return InValidateDetailsWindow(0);
}
static int32 EngineRenewUpdate(int32 p1)
{
DoCommandP(0, 0, _patches.autorenew, NULL, CMD_REPLACE_VEHICLE);
return 0;
}
static int32 EngineRenewMonthsUpdate(int32 p1)
{
DoCommandP(0, 1, _patches.autorenew_months, NULL, CMD_REPLACE_VEHICLE);
return 0;
}
static int32 EngineRenewMoneyUpdate(int32 p1)
{
DoCommandP(0, 2, _patches.autorenew_money, NULL, CMD_REPLACE_VEHICLE);
return 0;
}
typedef int32 PatchButtonClick(int32);
typedef struct PatchEntry {
byte type; // type of selector
byte flags; // selector flags
StringID str; // string with descriptive text
char console_name[40]; // the name this patch has in console
void *variable; // pointer to the variable
int32 min, max; // range for spinbox setting
uint32 step; // step for spinbox
PatchButtonClick *click_proc; // callback procedure
} PatchEntry;
enum {
PE_BOOL = 0,
PE_UINT8 = 1,
PE_INT16 = 2,
PE_UINT16 = 3,
PE_INT32 = 4,
PE_CURRENCY = 5,
// selector flags
PF_0ISDIS = 1 << 0, // a value of zero means the feature is disabled
PF_NOCOMMA = 1 << 1, // number without any thousand seperators
PF_MULTISTRING = 1 << 2, // string but only a limited number of options, so don't open editobx
PF_PLAYERBASED = 1 << 3, // This has to match the entries that are in settings.c, patch_player_settings
PF_NETWORK_ONLY = 1 << 4, // this setting only applies to network games
};
static const PatchEntry _patches_ui[] = {
{PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_VEHICLESPEED, "vehicle_speed", &_patches.vehicle_speed, 0, 0, 0, NULL},
{PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_LONGDATE, "long_date", &_patches.status_long_date, 0, 0, 0, NULL},
{PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_SHOWFINANCES, "show_finances", &_patches.show_finances, 0, 0, 0, NULL},
{PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_AUTOSCROLL, "autoscroll", &_patches.autoscroll, 0, 0, 0, NULL},
{PE_UINT8, PF_PLAYERBASED, STR_CONFIG_PATCHES_ERRMSG_DURATION, "errmsg_duration", &_patches.errmsg_duration, 0, 20, 1, NULL},
{PE_UINT8, PF_MULTISTRING | PF_PLAYERBASED, STR_CONFIG_PATCHES_TOOLBAR_POS, "toolbar_pos", &_patches.toolbar_pos, 0, 2, 1, &v_PositionMainToolbar},
{PE_UINT8, PF_0ISDIS | PF_PLAYERBASED, STR_CONFIG_PATCHES_SNAP_RADIUS, "window_snap_radius", &_patches.window_snap_radius, 1, 32, 1, NULL},
{PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_INVISIBLE_TREES, "invisible_trees", &_patches.invisible_trees, 0, 1, 1, &InvisibleTreesActive},
{PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_POPULATION_IN_LABEL, "population_in_label", &_patches.population_in_label, 0, 1, 1, &PopulationInLabelActive},
{PE_INT32, 0, STR_CONFIG_PATCHES_MAP_X, "map_x", &_patches.map_x, 6, 11, 1, NULL},
{PE_INT32, 0, STR_CONFIG_PATCHES_MAP_Y, "map_y", &_patches.map_y, 6, 11, 1, NULL},
};
static const PatchEntry _patches_construction[] = {
{PE_BOOL, 0, STR_CONFIG_PATCHES_BUILDONSLOPES, "build_on_slopes", &_patches.build_on_slopes, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_EXTRADYNAMITE, "extra_dynamite", &_patches.extra_dynamite, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_LONGBRIDGES, "long_bridges", &_patches.longbridges, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_SIGNALSIDE, "signal_side", &_patches.signal_side, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_SMALL_AIRPORTS, "always_small_airport", &_patches.always_small_airport, 0, 0, 0, NULL},
{PE_UINT8, PF_PLAYERBASED, STR_CONFIG_PATCHES_DRAG_SIGNALS_DENSITY, "drag_signals_density", &_patches.drag_signals_density, 1, 20, 1, NULL},
{PE_BOOL, 0, STR_CONFIG_AUTO_PBS_PLACEMENT, "auto_pbs_placement", &_patches.auto_pbs_placement, 1, 20, 1, NULL},
};
static const PatchEntry _patches_vehicles[] = {
{PE_BOOL, 0, STR_CONFIG_PATCHES_REALISTICACCEL, "realistic_acceleration", &_patches.realistic_acceleration, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_FORBID_90_DEG, "forbid_90_deg", &_patches.forbid_90_deg, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_MAMMOTHTRAINS, "mammoth_trains", &_patches.mammoth_trains, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_GOTODEPOT, "goto_depot", &_patches.gotodepot, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_ROADVEH_QUEUE, "roadveh_queue", &_patches.roadveh_queue, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_NEW_PATHFINDING_ALL, "new_pathfinding_all", &_patches.new_pathfinding_all, 0, 0, 0, NULL},
{PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_WARN_INCOME_LESS, "train_income_warn", &_patches.train_income_warn, 0, 0, 0, NULL},
{PE_UINT8, PF_MULTISTRING | PF_PLAYERBASED, STR_CONFIG_PATCHES_ORDER_REVIEW, "order_review_system", &_patches.order_review_system,0,2, 1, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_NEVER_EXPIRE_VEHICLES, "never_expire_vehicles", &_patches.never_expire_vehicles,0,0,0, NULL},
{PE_UINT16, PF_0ISDIS | PF_PLAYERBASED, STR_CONFIG_PATCHES_LOST_TRAIN_DAYS, "lost_train_days", &_patches.lost_train_days, 180,720, 60, NULL},
{PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_AUTORENEW_VEHICLE, "autorenew", &_patches.autorenew, 0, 0, 0, &EngineRenewUpdate},
{PE_INT16, PF_PLAYERBASED, STR_CONFIG_PATCHES_AUTORENEW_MONTHS, "autorenew_months", &_patches.autorenew_months, -12, 12, 1, &EngineRenewMonthsUpdate},
{PE_CURRENCY, PF_PLAYERBASED, STR_CONFIG_PATCHES_AUTORENEW_MONEY, "autorenew_money", &_patches.autorenew_money, 0, 2000000, 100000, &EngineRenewMoneyUpdate},
{PE_UINT16, 0, STR_CONFIG_PATCHES_MAX_TRAINS, "max_trains", &_patches.max_trains, 0,5000, 50, NULL},
{PE_UINT16, 0, STR_CONFIG_PATCHES_MAX_ROADVEH, "max_roadveh", &_patches.max_roadveh, 0,5000, 50, NULL},
{PE_UINT16, 0, STR_CONFIG_PATCHES_MAX_AIRCRAFT, "max_aircraft", &_patches.max_aircraft, 0,5000, 50, NULL},
{PE_UINT16, 0, STR_CONFIG_PATCHES_MAX_SHIPS, "max_ships", &_patches.max_ships, 0,5000, 50, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_SERVINT_ISPERCENT,"servint_isperfect",&_patches.servint_ispercent, 0, 0, 0, &CheckInterval},
{PE_UINT16, PF_0ISDIS, STR_CONFIG_PATCHES_SERVINT_TRAINS, "servint_trains", &_patches.servint_trains, 5,800, 5, &InValidateDetailsWindow},
{PE_UINT16, PF_0ISDIS, STR_CONFIG_PATCHES_SERVINT_ROADVEH, "servint_roadveh", &_patches.servint_roadveh, 5,800, 5, &InValidateDetailsWindow},
{PE_UINT16, PF_0ISDIS, STR_CONFIG_PATCHES_SERVINT_AIRCRAFT, "servint_aircraft", &_patches.servint_aircraft, 5,800, 5, &InValidateDetailsWindow},
{PE_UINT16, PF_0ISDIS, STR_CONFIG_PATCHES_SERVINT_SHIPS, "servint_ships", &_patches.servint_ships, 5,800, 5, &InValidateDetailsWindow},
{PE_BOOL, 0, STR_CONFIG_PATCHES_NOSERVICE, "no_servicing_if_no_breakdowns", &_patches.no_servicing_if_no_breakdowns, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_WAGONSPEEDLIMITS, "wagon_speed_limits", &_patches.wagon_speed_limits, 0, 0, 0, NULL},
};
static const PatchEntry _patches_stations[] = {
{PE_BOOL, 0, STR_CONFIG_PATCHES_JOINSTATIONS, "join_stations", &_patches.join_stations, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_FULLLOADANY, "full_load_any", &_patches.full_load_any, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_IMPROVEDLOAD, "improved_load", &_patches.improved_load, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_SELECTGOODS, "select_goods", &_patches.selectgoods, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_NEW_NONSTOP, "new_nonstop", &_patches.new_nonstop, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_NONUNIFORM_STATIONS, "nonuniform_stations", &_patches.nonuniform_stations, 0, 0, 0, NULL},
{PE_UINT8, 0, STR_CONFIG_PATCHES_STATION_SPREAD, "station_spread", &_patches.station_spread, 4, 64, 1, &InvalidateStationBuildWindow},
{PE_BOOL, 0, STR_CONFIG_PATCHES_SERVICEATHELIPAD, "service_at_helipad", &_patches.serviceathelipad, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_CATCHMENT, "modified_catchment", &_patches.modified_catchment, 0, 0, 0, NULL},
};
static const PatchEntry _patches_economy[] = {
{PE_BOOL, 0, STR_CONFIG_PATCHES_INFLATION, "inflation", &_patches.inflation, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_BUILDXTRAIND, "build_rawmaterial", &_patches.build_rawmaterial_ind, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_MULTIPINDTOWN, "multiple_industry_per_town", &_patches.multiple_industry_per_town,0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_SAMEINDCLOSE, "same_industry_close", &_patches.same_industry_close, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_BRIBE, "bribe", &_patches.bribe, 0, 0, 0, NULL},
{PE_UINT8, 0, STR_CONFIG_PATCHES_SNOWLINE_HEIGHT, "snow_line_height", &_patches.snow_line_height, 2, 13, 1, NULL},
{PE_INT32, PF_NOCOMMA, STR_CONFIG_PATCHES_COLORED_NEWS_DATE, "colored_new_data", &_patches.colored_news_date, 1900, 2200, 5, NULL},
{PE_INT32, PF_NOCOMMA, STR_CONFIG_PATCHES_STARTING_DATE, "starting_date", &_patches.starting_date, MAX_YEAR_BEGIN_REAL, MAX_YEAR_END_REAL, 1, NULL},
{PE_INT32, PF_NOCOMMA | PF_NETWORK_ONLY, STR_CONFIG_PATCHES_ENDING_DATE, "ending_date", &_patches.ending_date, MAX_YEAR_BEGIN_REAL, MAX_YEAR_END_REAL, 1, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_SMOOTH_ECONOMY, "smooth_economy", &_patches.smooth_economy, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_ALLOW_SHARES, "allow_shares", &_patches.allow_shares, 0, 0, 0, NULL},
};
static const PatchEntry _patches_ai[] = {
{PE_BOOL, 0, STR_CONFIG_PATCHES_AINEW_ACTIVE, "ainew_active", &_patches.ainew_active, 0, 1, 1, &AiNew_PatchActive_Warning},
{PE_BOOL, 0, STR_CONFIG_PATCHES_AI_BUILDS_TRAINS, "ai_disable_veh_train", &_patches.ai_disable_veh_train, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_AI_BUILDS_ROADVEH,"ai_disable_veh_roadveh",&_patches.ai_disable_veh_roadveh, 0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_AI_BUILDS_AIRCRAFT,"ai_disable_veh_aircraft",&_patches.ai_disable_veh_aircraft,0, 0, 0, NULL},
{PE_BOOL, 0, STR_CONFIG_PATCHES_AI_BUILDS_SHIPS,"ai_disable_veh_ship",&_patches.ai_disable_veh_ship, 0, 0, 0, NULL},
};
typedef struct PatchPage {
const PatchEntry *entries;
uint num;
} PatchPage;
static const PatchPage _patches_page[] = {
{_patches_ui, lengthof(_patches_ui) },
{_patches_construction, lengthof(_patches_construction) },
{_patches_vehicles, lengthof(_patches_vehicles) },
{_patches_stations, lengthof(_patches_stations) },
{_patches_economy, lengthof(_patches_economy) },
{_patches_ai, lengthof(_patches_ai) },
};
static int32 ReadPE(const PatchEntry*pe)
{
switch (pe->type) {
case PE_BOOL: return *(bool*)pe->variable;
case PE_UINT8: return *(uint8*)pe->variable;
case PE_INT16: return *(int16*)pe->variable;
case PE_UINT16: return *(uint16*)pe->variable;
case PE_INT32: return *(int32*)pe->variable;
case PE_CURRENCY: return (*(int32*)pe->variable) * GetCurrentCurrencyRate();
default: NOT_REACHED();
}
/* useless, but avoids compiler warning this way */
return 0;
}
static void WritePE(const PatchEntry *pe, int32 val)
{
if ((pe->flags & PF_0ISDIS) && val <= 0) {
switch (pe->type) {
case PE_BOOL: case PE_UINT8:
*(bool*)pe->variable = 0;
break;
case PE_INT16: case PE_UINT16:
*(int16*)pe->variable = 0;
break;
case PE_CURRENCY: case PE_INT32:
*(int32*)pe->variable = 0;
break;
}
return;
}
// "clamp" 'disabled' value to smallest type
switch (pe->type) {
case PE_BOOL:
*(bool*)pe->variable = (bool)val;
break;
case PE_UINT8:
*(uint8*)pe->variable = (uint8)clamp((uint8)val, (uint8)pe->min, (uint8)pe->max);
break;
case PE_INT16:
*(int16*)pe->variable = (int16)clamp((int16)val, (int16)pe->min, (int16)pe->max);
break;
case PE_UINT16:
*(uint16*)pe->variable = (uint16)clamp((uint16)val, (uint16)pe->min, (uint16)pe->max);
break;
case PE_CURRENCY: case PE_INT32:
*(int32*)pe->variable = (int32)clamp((int32)val, (int32)pe->min, (int32)pe->max);
break;
default: NOT_REACHED();
}
}
static void PatchesSelectionWndProc(Window *w, WindowEvent *e)
{
uint i;
switch (e->event) {
case WE_PAINT: {
int x,y;
const PatchEntry *pe;
const PatchPage *page;
uint clk;
int32 val;
w->click_state = 1 << (WP(w,def_d).data_1 + 4);
DrawWindowWidgets(w);
x = 0;
y = 46;
clk = WP(w,def_d).data_2;
page = &_patches_page[WP(w,def_d).data_1];
for (i = 0, pe = page->entries; i != page->num; i++, pe++) {
bool disabled = false;
bool editable = true;
if ((pe->flags & PF_NETWORK_ONLY) && !_networking)
editable = false;
// We do not allow changes of some items when we are a client in a networkgame
if (!(pe->flags & PF_PLAYERBASED) && _networking && !_network_server)
editable = false;
if (pe->type == PE_BOOL) {
if (editable)
DrawFrameRect(x+5, y+1, x+15+9, y+9, (*(bool*)pe->variable) ? 6 : 4, (*(bool*)pe->variable) ? FR_LOWERED : 0);
else
DrawFrameRect(x+5, y+1, x+15+9, y+9, (*(bool*)pe->variable) ? 7 : 9, (*(bool*)pe->variable) ? FR_LOWERED : 0);
SetDParam(0, *(bool*)pe->variable ? STR_CONFIG_PATCHES_ON : STR_CONFIG_PATCHES_OFF);
} else {
DrawFrameRect(x+5, y+1, x+5+9, y+9, 3, clk == i*2+1 ? FR_LOWERED : 0);
DrawFrameRect(x+15, y+1, x+15+9, y+9, 3, clk == i*2+2 ? FR_LOWERED : 0);
if (!editable) {
int color = PALETTE_MODIFIER_GREYOUT | _color_list[3].unk2;
GfxFillRect(x+6, y+2, x+6+8, y+9, color);
GfxFillRect(x+16, y+2, x+16+8, y+9, color);
}
DrawStringCentered(x+10, y+1, STR_6819, 0);
DrawStringCentered(x+20, y+1, STR_681A, 0);
val = ReadPE(pe);
if (pe->type == PE_CURRENCY)
val /= GetCurrentCurrencyRate();
disabled = ((val == 0) && (pe->flags & PF_0ISDIS));
if (disabled) {
SetDParam(0, STR_CONFIG_PATCHES_DISABLED);
} else {
SetDParam(1, val);
if (pe->type == PE_CURRENCY)
SetDParam(0, STR_CONFIG_PATCHES_CURRENCY);
else {
if (pe->flags & PF_MULTISTRING)
SetDParam(0, pe->str + val + 1);
else
SetDParam(0, pe->flags & PF_NOCOMMA ? STR_CONFIG_PATCHES_INT32 : STR_7024);
}
}
}
DrawString(30, y+1, (pe->str)+disabled, 0);
y += 11;
}
break;
}
case WE_CLICK:
switch(e->click.widget) {
case 3: {
int x,y;
uint btn;
const PatchPage *page;
const PatchEntry *pe;
y = e->click.pt.y - 46 - 1;
if (y < 0) return;
btn = y / 11;
if (y % 11 > 9) return;
page = &_patches_page[WP(w,def_d).data_1];
if (btn >= page->num) return;
pe = &page->entries[btn];
x = e->click.pt.x - 5;
if (x < 0) return;
if (((pe->flags & PF_NETWORK_ONLY) && !_networking) || // return if action is only active in network
(!(pe->flags & PF_PLAYERBASED) && _networking && !_network_server)) // return if only server can change it
return;
if (x < 21) { // clicked on the icon on the left side. Either scroller or bool on/off
int32 val = ReadPE(pe), oval = val;
switch(pe->type) {
case PE_BOOL:
val ^= 1;
break;
case PE_UINT8:
case PE_INT16:
case PE_UINT16:
case PE_INT32:
case PE_CURRENCY:
// don't allow too fast scrolling
if ((w->flags4 & WF_TIMEOUT_MASK) > 2 << WF_TIMEOUT_SHL) {
_left_button_clicked = false;
return;
}
if (x >= 10) {
//increase
if (pe->flags & PF_0ISDIS && val == 0)
val = pe->min;
else
val += pe->step;
if (val > pe->max) val = pe->max;
} else {
// decrease
if (val <= pe->min && pe->flags & PF_0ISDIS) {
val = 0;
} else {
val -= pe->step;
if (val < pe->min) val = pe->min;
}
}
if (val != oval) {
WP(w,def_d).data_2 = btn * 2 + 1 + ((x>=10) ? 1 : 0);
w->flags4 |= 5 << WF_TIMEOUT_SHL;
_left_button_clicked = false;
}
break;
}
if (val != oval) {
// To make patch-changes network-safe
if (pe->type == PE_CURRENCY) {
val /= GetCurrentCurrencyRate();
}
// If an item is playerbased, we do not send it over the network (if any)
if (pe->flags & PF_PLAYERBASED) {
WritePE(pe, val);
} else {
// Else we do
DoCommandP(0, (byte)WP(w,def_d).data_1 + ((byte)btn << 8), val, NULL, CMD_CHANGE_PATCH_SETTING);
}
SetWindowDirty(w);
if (pe->click_proc != NULL) // call callback function
pe->click_proc(val);
}
} else {
if (pe->type != PE_BOOL && !(pe->flags & PF_MULTISTRING)) { // do not open editbox
WP(w,def_d).data_3 = btn;
SetDParam(0, ReadPE(pe));
ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_CONFIG_PATCHES_QUERY_CAPT, 10, 100, WC_GAME_OPTIONS, 0);
}
}
break;
}
case 4: case 5: case 6: case 7: case 8: case 9:
WP(w,def_d).data_1 = e->click.widget - 4;
DeleteWindowById(WC_QUERY_STRING, 0);
SetWindowDirty(w);
break;
}
break;
case WE_TIMEOUT:
WP(w,def_d).data_2 = 0;
SetWindowDirty(w);
break;
case WE_ON_EDIT_TEXT: {
if (*e->edittext.str) {
const PatchPage *page = &_patches_page[WP(w,def_d).data_1];
const PatchEntry *pe = &page->entries[WP(w,def_d).data_3];
int32 val;
val = atoi(e->edittext.str);
if (pe->type == PE_CURRENCY) {
val /= GetCurrentCurrencyRate();
}
// If an item is playerbased, we do not send it over the network (if any)
if (pe->flags & PF_PLAYERBASED) {
WritePE(pe, val);
} else {
// Else we do
DoCommandP(0, (byte)WP(w,def_d).data_1 + ((byte)WP(w,def_d).data_3 << 8), val, NULL, CMD_CHANGE_PATCH_SETTING);
}
SetWindowDirty(w);
if (pe->click_proc != NULL) // call callback function
pe->click_proc(*(int32*)pe->variable);
}
break;
}
case WE_DESTROY:
DeleteWindowById(WC_QUERY_STRING, 0);
break;
}
}
/** Network-safe changing of patch-settings.
* @param p1 various bitstuffed elements
* - p1 = (bit 0- 7) - the patches type (page) that is being changed (construction, network, ai)
* - p2 = (bit 8-15) - the actual patch (entry) being set inside the category
* @param p2 the new value for the patch
* @todo check that the new value is a valid one. Awful lot of work, but since only
* the server is allowed to do this, we trust it on this one :)
*/
int32 CmdChangePatchSetting(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
byte pcat = GB(p1, 0, 8);
byte pel = GB(p1, 8, 8);
if (pcat >= lengthof(_patches_page)) return CMD_ERROR;
if (pel >= _patches_page[pcat].num) return CMD_ERROR;
if (flags & DC_EXEC) {
const PatchEntry *pe = &_patches_page[pcat].entries[pel];
WritePE(pe, (int32)p2);
InvalidateWindow(WC_GAME_OPTIONS, 0);
}
return 0;
}
static const PatchEntry *IConsoleGetPatch(const char *name, uint *page, uint *entry)
{
const PatchPage *pp;
const PatchEntry *pe;
for (*page = 0; *page < lengthof(_patches_page); (*page)++) {
pp = &_patches_page[*page];
for (*entry = 0; *entry < pp->num; (*entry)++) {
pe = &pp->entries[*entry];
if (strncmp(pe->console_name, name, sizeof(pe->console_name)) == 0)
return pe;
}
}
return NULL;
}
/* Those 2 functions need to be here, else we have to make some stuff non-static
and besides, it is also better to keep stuff like this at the same place */
void IConsoleSetPatchSetting(const char *name, const char *value)
{
const PatchEntry *pe;
uint page, entry;
int val;
pe = IConsoleGetPatch(name, &page, &entry);
if (pe == NULL) {
IConsolePrintF(_icolour_warn, "'%s' is an unknown patch setting.", name);
return;
}
sscanf(value, "%d", &val);
if (pe->type == PE_CURRENCY) // currency can be different on each client
val /= GetCurrentCurrencyRate();
// If an item is playerbased, we do not send it over the network (if any)
if (pe->flags & PF_PLAYERBASED) {
WritePE(pe, val);
} else // Else we do
DoCommandP(0, page + (entry << 8), val, NULL, CMD_CHANGE_PATCH_SETTING);
{
char tval[20];
const char *tval2 = value;
if (pe->type == PE_BOOL) {
snprintf(tval, sizeof(tval), (val == 1) ? "on" : "off");
tval2 = tval;
}
IConsolePrintF(_icolour_warn, "'%s' changed to: %s", name, tval2);
}
}
void IConsoleGetPatchSetting(const char *name)
{
char value[20];
uint page, entry;
const PatchEntry *pe = IConsoleGetPatch(name, &page, &entry);
/* We did not find the patch setting */
if (pe == NULL) {
IConsolePrintF(_icolour_warn, "'%s' is an unknown patch setting.", name);
return;
}
if (pe->type == PE_BOOL) {
snprintf(value, sizeof(value), (ReadPE(pe) == 1) ? "on" : "off");
} else
snprintf(value, sizeof(value), "%d", ReadPE(pe));
IConsolePrintF(_icolour_warn, "Current value for '%s' is: '%s'", name, value);
}
static const Widget _patches_selection_widgets[] = {
{ WWT_CLOSEBOX, RESIZE_NONE, 10, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW},
{ WWT_CAPTION, RESIZE_NONE, 10, 11, 369, 0, 13, STR_CONFIG_PATCHES_CAPTION, STR_018C_WINDOW_TITLE_DRAG_THIS},
{ WWT_PANEL, RESIZE_NONE, 10, 0, 369, 14, 41, 0x0, STR_NULL},
{ WWT_PANEL, RESIZE_NONE, 10, 0, 369, 42, 320, 0x0, STR_NULL},
{ WWT_CLOSEBOX, RESIZE_NONE, 3, 10, 96, 16, 27, STR_CONFIG_PATCHES_GUI, STR_NULL},
{ WWT_CLOSEBOX, RESIZE_NONE, 3, 97, 183, 16, 27, STR_CONFIG_PATCHES_CONSTRUCTION, STR_NULL},
{ WWT_CLOSEBOX, RESIZE_NONE, 3, 184, 270, 16, 27, STR_CONFIG_PATCHES_VEHICLES, STR_NULL},
{ WWT_CLOSEBOX, RESIZE_NONE, 3, 271, 357, 16, 27, STR_CONFIG_PATCHES_STATIONS, STR_NULL},
{ WWT_CLOSEBOX, RESIZE_NONE, 3, 10, 96, 28, 39, STR_CONFIG_PATCHES_ECONOMY, STR_NULL},
{ WWT_CLOSEBOX, RESIZE_NONE, 3, 97, 183, 28, 39, STR_CONFIG_PATCHES_AI, STR_NULL},
{ WIDGETS_END},
};
static const WindowDesc _patches_selection_desc = {
WDP_CENTER, WDP_CENTER, 370, 321,
WC_GAME_OPTIONS,0,
WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
_patches_selection_widgets,
PatchesSelectionWndProc,
};
void ShowPatchesSelection(void)
{
DeleteWindowById(WC_GAME_OPTIONS, 0);
AllocateWindowDesc(&_patches_selection_desc);
}
enum {
NEWGRF_WND_PROC_OFFSET_TOP_WIDGET = 14,
NEWGRF_WND_PROC_ROWSIZE = 14
};
static void NewgrfWndProc(Window *w, WindowEvent *e)
{
static GRFFile *_sel_grffile;
switch (e->event) {
case WE_PAINT: {
int x, y = NEWGRF_WND_PROC_OFFSET_TOP_WIDGET;
uint16 i = 0;
GRFFile *c = _first_grffile;
DrawWindowWidgets(w);
if (_first_grffile == NULL) { // no grf sets installed
DrawStringMultiCenter(140, 210, STR_NEWGRF_NO_FILES_INSTALLED, 250);
break;
}
// draw list of all grf files
while (c != NULL) {
if (i >= w->vscroll.pos) { // draw files according to scrollbar position
bool h = (_sel_grffile==c);
// show highlighted item with a different background and highlighted text
if(h) GfxFillRect(1, y + 1, 267, y + 12, 156);
// XXX - will be grf name later
DoDrawString(c->filename, 25, y + 2, h ? 0xC : 0x10);
DrawSprite(SPRITE_PALETTE(SPR_SQUARE | PALETTE_TO_RED), 5, y + 2);
y += NEWGRF_WND_PROC_ROWSIZE;
}
c = c->next;
if (++i == w->vscroll.cap + w->vscroll.pos) break; // stop after displaying 12 items
}
// DoDrawString(_sel_grffile->setname, 120, 200, 0x01); // draw grf name
if (_sel_grffile == NULL) { // no grf file selected yet
DrawStringMultiCenter(140, 210, STR_NEWGRF_TIP, 250);
} else {
// draw filename
x = DrawString(5, 199, STR_NEWGRF_FILENAME, 0);
DoDrawString(_sel_grffile->filename, x + 2, 199, 0x01);
// draw grf id
x = DrawString(5, 209, STR_NEWGRF_GRF_ID, 0);
snprintf(_userstring, lengthof(_userstring), "%08X", _sel_grffile->grfid);
DrawString(x + 2, 209, STR_SPEC_USERSTRING, 0x01);
}
} break;
case WE_CLICK:
switch(e->click.widget) {
case 3: { // select a grf file
int y = (e->click.pt.y - NEWGRF_WND_PROC_OFFSET_TOP_WIDGET) / NEWGRF_WND_PROC_ROWSIZE;
if (y >= w->vscroll.cap) { return;} // click out of bounds
y += w->vscroll.pos;
if (y >= _grffile_count) return;
_sel_grffile = _first_grffile;
// get selected grf-file
while (y-- != 0) _sel_grffile = _sel_grffile->next;
SetWindowDirty(w);
} break;
case 9: /* Cancel button */
DeleteWindowById(WC_GAME_OPTIONS, 0);
break;
} break;
/* Parameter edit box not used yet
case WE_TIMEOUT:
WP(w,def_d).data_2 = 0;
SetWindowDirty(w);
break;
case WE_ON_EDIT_TEXT: {
if (*e->edittext.str) {
SetWindowDirty(w);
}
break;
}
*/
case WE_DESTROY:
_sel_grffile = NULL;
DeleteWindowById(WC_QUERY_STRING, 0);
break;
}
}
static const Widget _newgrf_widgets[] = {
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW},
{ WWT_CAPTION, RESIZE_NONE, 14, 11, 279, 0, 13, STR_NEWGRF_SETTINGS_CAPTION, STR_018C_WINDOW_TITLE_DRAG_THIS},
{ WWT_PANEL, RESIZE_NONE, 14, 0, 279, 183, 276, 0x0, STR_NULL},
{ WWT_MATRIX, RESIZE_NONE, 14, 0, 267, 14, 182, 0xC01,/*small rows*/ STR_NEWGRF_TIP},
{ WWT_SCROLLBAR, RESIZE_NONE, 14, 268, 279, 14, 182, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST},
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 147, 158, 244, 255, STR_0188, STR_NULL},
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 159, 170, 244, 255, STR_0189, STR_NULL},
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 175, 274, 244, 255, STR_NEWGRF_SET_PARAMETERS, STR_NULL},
{ WWT_PUSHTXTBTN, RESIZE_NONE, 3, 5, 138, 261, 272, STR_NEWGRF_APPLY_CHANGES, STR_NULL},
{ WWT_PUSHTXTBTN, RESIZE_NONE, 3, 142, 274, 261, 272, STR_012E_CANCEL, STR_NULL},
{ WIDGETS_END},
};
static const WindowDesc _newgrf_desc = {
WDP_CENTER, WDP_CENTER, 280, 277,
WC_GAME_OPTIONS,0,
WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
_newgrf_widgets,
NewgrfWndProc,
};
void ShowNewgrf(void)
{
Window *w;
DeleteWindowById(WC_GAME_OPTIONS, 0);
w = AllocateWindowDesc(&_newgrf_desc);
{ // little helper function to calculate _grffile_count
// should be REMOVED once _grffile_count is calculated at loading
GRFFile *c = _first_grffile;
_grffile_count = 0;
while (c != NULL) {
_grffile_count++;
c = c->next;
}
}
w->vscroll.cap = 12;
w->vscroll.count = _grffile_count;
w->vscroll.pos = 0;
w->disabled_state = (1 << 5) | (1 << 6) | (1 << 7);
}
/* state: 0 = none clicked, 0x01 = first clicked, 0x02 = second clicked */
void DrawArrowButtons(int x, int y, int state)
{
DrawFrameRect(x, y+1, x+9, y+9, 3, (state & 0x01) ? FR_LOWERED : 0);
DrawFrameRect(x+10, y+1, x+19, y+9, 3, (state & 0x02) ? FR_LOWERED : 0);
DrawStringCentered(x+5, y+1, STR_6819, 0);
DrawStringCentered(x+15, y+1, STR_681A, 0);
}
static char _str_separator[2];
static void CustCurrencyWndProc(Window *w, WindowEvent *e)
{
switch (e->event) {
case WE_PAINT: {
int x=35, y=20, i=0;
int clk = WP(w,def_d).data_1;
DrawWindowWidgets(w);
// exchange rate
DrawArrowButtons(10, y, (clk >> (i*2)) & 0x03);
SetDParam(0, 1);
SetDParam(1, 1);
DrawString(x, y + 1, STR_CURRENCY_EXCHANGE_RATE, 0);
x = 35;
y+=12;
i++;
// separator
DrawFrameRect(10, y+1, 29, y+9, 0, ((clk >> (i*2)) & 0x03) ? FR_LOWERED : 0);
x = DrawString(x, y + 1, STR_CURRENCY_SEPARATOR, 0);
DoDrawString(_str_separator, x + 4, y + 1, 6);
x = 35;
y+=12;
i++;
// prefix
DrawFrameRect(10, y+1, 29, y+9, 0, ((clk >> (i*2)) & 0x03) ? FR_LOWERED : 0);
x = DrawString(x, y + 1, STR_CURRENCY_PREFIX, 0);
DoDrawString(_custom_currency.prefix, x + 4, y + 1, 6);
x = 35;
y+=12;
i++;
// suffix
DrawFrameRect(10, y+1, 29, y+9, 0, ((clk >> (i*2)) & 0x03) ? FR_LOWERED : 0);
x = DrawString(x, y + 1, STR_CURRENCY_SUFFIX, 0);
DoDrawString(_custom_currency.suffix, x + 4, y + 1, 6);
x = 35;
y+=12;
i++;
// switch to euro
DrawArrowButtons(10, y, (clk >> (i*2)) & 0x03);
SetDParam(0, _custom_currency.to_euro);
DrawString(x, y + 1, (_custom_currency.to_euro != CF_NOEURO) ? STR_CURRENCY_SWITCH_TO_EURO : STR_CURRENCY_SWITCH_TO_EURO_NEVER, 0);
x = 35;
y+=12;
i++;
// Preview
y+=12;
SetDParam(0, 10000);
DrawString(x, y + 1, STR_CURRENCY_PREVIEW, 0);
} break;
case WE_CLICK: {
bool edittext = false;
int line = (e->click.pt.y - 20)/12;
int len = 0;
int x = e->click.pt.x;
StringID str = 0;
switch ( line ) {
case 0: // rate
if ( IS_INT_INSIDE(x, 10, 30) ) { // clicked buttons
if (x < 20) {
if (_custom_currency.rate > 1) _custom_currency.rate--;
WP(w,def_d).data_1 = (1 << (line * 2 + 0));
} else {
if (_custom_currency.rate < 5000) _custom_currency.rate++;
WP(w,def_d).data_1 = (1 << (line * 2 + 1));
}
} else { // enter text
SetDParam(0, _custom_currency.rate);
str = STR_CONFIG_PATCHES_INT32;
len = 4;
edittext = true;
}
break;
case 1: // separator
if ( IS_INT_INSIDE(x, 10, 30) ) // clicked button
WP(w,def_d).data_1 = (1 << (line * 2 + 1));
str = BindCString(_str_separator);
len = 1;
edittext = true;
break;
case 2: // prefix
if ( IS_INT_INSIDE(x, 10, 30) ) // clicked button
WP(w,def_d).data_1 = (1 << (line * 2 + 1));
str = BindCString(_custom_currency.prefix);
len = 12;
edittext = true;
break;
case 3: // suffix
if ( IS_INT_INSIDE(x, 10, 30) ) // clicked button
WP(w,def_d).data_1 = (1 << (line * 2 + 1));
str = BindCString(_custom_currency.suffix);
len = 12;
edittext = true;
break;
case 4: // to euro
if ( IS_INT_INSIDE(x, 10, 30) ) { // clicked buttons
if (x < 20) {
_custom_currency.to_euro = (_custom_currency.to_euro <= 2000) ?
CF_NOEURO : _custom_currency.to_euro - 1;
WP(w,def_d).data_1 = (1 << (line * 2 + 0));
} else {
_custom_currency.to_euro =
clamp(_custom_currency.to_euro + 1, 2000, MAX_YEAR_END_REAL);
WP(w,def_d).data_1 = (1 << (line * 2 + 1));
}
} else { // enter text
SetDParam(0, _custom_currency.to_euro);
str = STR_CONFIG_PATCHES_INT32;
len = 4;
edittext = true;
}
break;
}
if (edittext) {
WP(w,def_d).data_2 = line;
ShowQueryString(
str,
STR_CURRENCY_CHANGE_PARAMETER,
len + 1, // maximum number of characters OR
250, // characters up to this width pixels, whichever is satisfied first
w->window_class,
w->window_number);
}
w->flags4 |= 5 << WF_TIMEOUT_SHL;
SetWindowDirty(w);
} break;
case WE_ON_EDIT_TEXT: {
int val;
const char *b = e->edittext.str;
switch (WP(w,def_d).data_2) {
case 0: /* Exchange rate */
val = atoi(b);
val = clamp(val, 1, 5000);
_custom_currency.rate = val;
break;
case 1: /* Thousands seperator */
_custom_currency.separator = (b[0] == '\0') ? ' ' : b[0];
ttd_strlcpy(_str_separator, b, lengthof(_str_separator));
break;
case 2: /* Currency prefix */
ttd_strlcpy(_custom_currency.prefix, b, lengthof(_custom_currency.prefix));
break;
case 3: /* Currency suffix */
ttd_strlcpy(_custom_currency.suffix, b, lengthof(_custom_currency.suffix));
break;
case 4: /* Year to switch to euro */
val = atoi(b);
val = clamp(val, 1999, MAX_YEAR_END_REAL);
if (val == 1999) val = 0;
_custom_currency.to_euro = val;
break;
}
MarkWholeScreenDirty();
} break;
case WE_TIMEOUT:
WP(w,def_d).data_1 = 0;
SetWindowDirty(w);
break;
case WE_DESTROY:
DeleteWindowById(WC_QUERY_STRING, 0);
MarkWholeScreenDirty();
break;
}
}
static const Widget _cust_currency_widgets[] = {
{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW},
{ WWT_CAPTION, RESIZE_NONE, 14, 11, 229, 0, 13, STR_CURRENCY_WINDOW, STR_018C_WINDOW_TITLE_DRAG_THIS},
{ WWT_PANEL, RESIZE_NONE, 14, 0, 229, 14, 119, 0x0, STR_NULL},
{ WIDGETS_END},
};
static const WindowDesc _cust_currency_desc = {
WDP_CENTER, WDP_CENTER, 230, 120,
WC_CUSTOM_CURRENCY, 0,
WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
_cust_currency_widgets,
CustCurrencyWndProc,
};
void ShowCustCurrency(void)
{
Window *w;
_str_separator[0] = _custom_currency.separator;
_str_separator[1] = '\0';
DeleteWindowById(WC_CUSTOM_CURRENCY, 0);
w = AllocateWindowDesc(&_cust_currency_desc);
}