4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
10 /** @file main_gui.cpp Handling of the main viewport. */
14 #include "spritecache.h"
15 #include "window_gui.h"
16 #include "window_func.h"
17 #include "textbuf_gui.h"
18 #include "viewport_func.h"
19 #include "command_func.h"
20 #include "console_gui.h"
22 #include "transparency_gui.h"
23 #include "sound_func.h"
24 #include "transparency.h"
25 #include "strings_func.h"
26 #include "zoom_func.h"
27 #include "company_base.h"
28 #include "company_func.h"
29 #include "toolbar_gui.h"
30 #include "statusbar_gui.h"
31 #include "linkgraph/linkgraph_gui.h"
32 #include "tilehighlight_func.h"
35 #include "saveload/saveload.h"
37 #include "widgets/main_widget.h"
39 #include "network/network.h"
40 #include "network/network_func.h"
41 #include "network/network_gui.h"
42 #include "network/network_base.h"
44 #include "table/sprites.h"
45 #include "table/strings.h"
47 static int _rename_id
= 1;
48 static int _rename_what
= -1;
50 void CcGiveMoney(const CommandCost
&result
, TileIndex tile
, uint32 p1
, uint32 p2
)
53 if (result
.Failed() || !_settings_game
.economy
.give_money
) return;
55 /* Inform the company of the action of one of its clients (controllers). */
58 GetString (msg
, STR_COMPANY_NAME
);
60 if (!_network_server
) {
61 NetworkClientSendChat(NETWORK_ACTION_GIVE_MONEY
, DESTTYPE_TEAM
, p2
, msg
, p1
);
63 NetworkServerSendChat(NETWORK_ACTION_GIVE_MONEY
, DESTTYPE_TEAM
, p2
, msg
, CLIENT_ID_SERVER
, p1
);
65 #endif /* ENABLE_NETWORK */
68 void HandleOnEditText(const char *str
)
70 switch (_rename_what
) {
72 case 3: { // Give money, you can only give money in excess of loan
73 const Company
*c
= Company::GetIfValid(_local_company
);
75 Money money
= min(c
->money
- c
->current_loan
, (Money
)(atoi(str
) / _currency
->rate
));
77 uint32 money_c
= Clamp(ClampToI32(money
), 0, 20000000); // Clamp between 20 million and 0
79 /* Give 'id' the money, and subtract it from ourself */
80 DoCommandP(0, money_c
, _rename_id
, CMD_GIVE_MONEY
, str
);
83 #endif /* ENABLE_NETWORK */
84 default: NOT_REACHED();
87 _rename_id
= _rename_what
= -1;
91 * This code is shared for the majority of the pushbuttons.
92 * Handles e.g. the pressing of a button (to build things), playing of click sound and sets certain parameters
94 * @param w Window which called the function
95 * @param widget ID of the widget (=button) that called this function
96 * @param cursor How should the cursor image change? E.g. cursor with depot image in it
97 * @return true if the button is clicked, false if it's unclicked
98 * @param mode Pointer mode to set.
100 bool HandlePlacePushButton (Window
*w
, int widget
, CursorID cursor
, PointerMode mode
)
102 if (w
->IsWidgetDisabled(widget
)) return false;
104 if (_settings_client
.sound
.click_beep
) SndPlayFx(SND_15_BEEP
);
107 if (w
->IsWidgetLowered(widget
)) {
112 SetPointerMode (mode
, w
->window_class
, w
->window_number
, cursor
);
113 w
->LowerWidget(widget
);
118 void CcPlaySound_EXPLOSION(const CommandCost
&result
, TileIndex tile
, uint32 p1
, uint32 p2
)
120 if (result
.Succeeded() && _settings_client
.sound
.confirm
) SndPlayTileFx(SND_12_EXPLOSION
, tile
);
123 #ifdef ENABLE_NETWORK
124 void ShowNetworkGiveMoneyWindow(CompanyID company
)
126 _rename_id
= company
;
128 ShowQueryString(STR_EMPTY
, STR_NETWORK_GIVE_MONEY_CAPTION
, 30, NULL
, CS_NUMERAL
, QSF_NONE
);
130 #endif /* ENABLE_NETWORK */
134 * Zoom a viewport in or out.
135 * @param vp Viewport to zoom.
136 * @param in Whether to zoom in, else out.
138 void DoZoomInOutViewport (ViewportData
*vp
, bool in
)
141 vp
->zoom
= (ZoomLevel
)((int)vp
->zoom
- 1);
142 vp
->virtual_width
>>= 1;
143 vp
->virtual_height
>>= 1;
145 vp
->scrollpos_x
+= vp
->virtual_width
>> 1;
146 vp
->scrollpos_y
+= vp
->virtual_height
>> 1;
148 vp
->zoom
= (ZoomLevel
)((int)vp
->zoom
+ 1);
150 vp
->scrollpos_x
-= vp
->virtual_width
>> 1;
151 vp
->scrollpos_y
-= vp
->virtual_height
>> 1;
153 vp
->virtual_width
<<= 1;
154 vp
->virtual_height
<<= 1;
157 vp
->dest_scrollpos_x
= vp
->scrollpos_x
;
158 vp
->dest_scrollpos_y
= vp
->scrollpos_y
;
160 vp
->follow_vehicle
= INVALID_VEHICLE
;
162 vp
->virtual_left
= vp
->scrollpos_x
;
163 vp
->virtual_top
= vp
->scrollpos_y
;
166 void ClampViewportZoom (ViewportData
*vp
)
168 if (vp
->zoom
< _settings_client
.gui
.zoom_min
) {
169 do DoZoomInOutViewport (vp
, false);
170 while (vp
->zoom
< _settings_client
.gui
.zoom_min
);
171 } else if (vp
->zoom
> _settings_client
.gui
.zoom_max
) {
172 do DoZoomInOutViewport (vp
, true);
173 while (vp
->zoom
> _settings_client
.gui
.zoom_max
);
177 static const struct NWidgetPart _nested_main_window_widgets
[] = {
178 NWidget(NWID_VIEWPORT
, INVALID_COLOUR
, WID_M_VIEWPORT
), SetResize(1, 1),
189 GHK_RESET_OBJECT_TO_PLACE
,
191 GHK_DELETE_NONVITAL_WINDOWS
,
196 GHK_TOGGLE_TRANSPARENCY
,
197 GHK_TOGGLE_INVISIBILITY
= GHK_TOGGLE_TRANSPARENCY
+ 9,
198 GHK_TRANSPARENCY_TOOLBAR
= GHK_TOGGLE_INVISIBILITY
+ 8,
206 struct MainWindow
: Window
208 LinkGraphOverlay overlay
;
211 static const uint LINKGRAPH_REFRESH_PERIOD
= 0xff;
212 static const uint LINKGRAPH_DELAY
= 0xf;
214 MainWindow (const WindowDesc
*desc
) : Window (desc
),
215 overlay (this, WID_M_VIEWPORT
, 0, 0, 3), refresh (0)
218 CLRBITS(this->flags
, WF_WHITE_BORDER
);
219 ResizeWindow (this, _screen_width
, _screen_height
);
221 NWidgetViewport
*nvp
= this->GetWidget
<NWidgetViewport
>(WID_M_VIEWPORT
);
222 nvp
->InitializeViewport(this, TileXY(32, 32), ZOOM_LVL_VIEWPORT
);
224 this->viewport
->overlay
= &this->overlay
;
225 this->refresh
= LINKGRAPH_DELAY
;
228 virtual void OnTick()
230 if (--this->refresh
> 0) return;
232 this->refresh
= LINKGRAPH_REFRESH_PERIOD
;
234 if (this->overlay
.GetCargoMask() == 0 ||
235 this->overlay
.GetCompanyMask() == 0) {
239 this->overlay
.RebuildCache();
240 this->GetWidget
<NWidgetBase
>(WID_M_VIEWPORT
)->SetDirty(this);
243 void OnPaint (BlitArea
*dpi
) OVERRIDE
245 this->DrawWidgets (dpi
);
246 if (_game_mode
== GM_MENU
) {
247 static const SpriteID title_sprites
[] = {SPR_OTTD_O
, SPR_OTTD_P
, SPR_OTTD_E
, SPR_OTTD_N
, SPR_OTTD_T
, SPR_OTTD_T
, SPR_OTTD_D
};
248 static const uint LETTER_SPACING
= 10;
249 int name_width
= (lengthof(title_sprites
) - 1) * LETTER_SPACING
;
251 for (uint i
= 0; i
< lengthof(title_sprites
); i
++) {
252 name_width
+= GetSpriteSize(title_sprites
[i
]).width
;
254 int off_x
= (this->width
- name_width
) / 2;
256 for (uint i
= 0; i
< lengthof(title_sprites
); i
++) {
257 DrawSprite (dpi
, title_sprites
[i
], PAL_NONE
, off_x
, 50);
258 off_x
+= GetSpriteSize(title_sprites
[i
]).width
+ LETTER_SPACING
;
263 bool OnHotkey (int hotkey
) OVERRIDE
265 if (hotkey
== GHK_QUIT
) {
266 HandleExitGameRequest();
270 /* Disable all key shortcuts, except quit shortcuts when
271 * generating the world, otherwise they create threading
272 * problem during the generating, resulting in random
273 * assertions that are hard to trigger and debug */
274 if (HasModalProgress()) return false;
278 /* No point returning from the main menu to itself */
279 if (_game_mode
== GM_MENU
) return true;
280 if (_settings_client
.gui
.autosave_on_exit
) {
282 _switch_mode
= SM_MENU
;
292 case GHK_BOUNDING_BOXES
:
293 ToggleBoundingBoxes();
296 case GHK_DIRTY_BLOCKS
:
301 if (_game_mode
== GM_MENU
) return false;
305 case GHK_CENTER_ZOOM
: {
306 Point pt
= GetTileBelowCursor();
308 bool instant
= (hotkey
== GHK_CENTER_ZOOM
&& this->viewport
->zoom
!= _settings_client
.gui
.zoom_min
);
310 do DoZoomInOutViewport (this->viewport
, true);
311 while (this->viewport
->zoom
> _settings_client
.gui
.zoom_min
);
312 this->InvalidateData();
314 ScrollMainWindowTo(pt
.x
, pt
.y
, -1, instant
);
319 case GHK_RESET_OBJECT_TO_PLACE
: ResetPointerMode(); break;
320 case GHK_DELETE_WINDOWS
: DeleteNonVitalWindows(); break;
321 case GHK_DELETE_NONVITAL_WINDOWS
: DeleteAllNonVitalWindows(); break;
322 case GHK_REFRESH_SCREEN
: MarkWholeScreenDirty(); break;
324 case GHK_CRASH
: // Crash the game
325 *(volatile byte
*)0 = 0;
328 case GHK_MONEY
: // Gimme money
329 /* You can only cheat for money in single player. */
330 if (!_networking
) DoCommandP(0, 10000000, 0, CMD_MONEY_CHEAT
);
333 case GHK_UPDATE_COORDS
: // Update the coordinates of all station signs
334 UpdateAllVirtCoords();
337 case GHK_TOGGLE_TRANSPARENCY
:
338 case GHK_TOGGLE_TRANSPARENCY
+ 1:
339 case GHK_TOGGLE_TRANSPARENCY
+ 2:
340 case GHK_TOGGLE_TRANSPARENCY
+ 3:
341 case GHK_TOGGLE_TRANSPARENCY
+ 4:
342 case GHK_TOGGLE_TRANSPARENCY
+ 5:
343 case GHK_TOGGLE_TRANSPARENCY
+ 6:
344 case GHK_TOGGLE_TRANSPARENCY
+ 7:
345 case GHK_TOGGLE_TRANSPARENCY
+ 8:
346 /* Transparency toggle hot keys */
347 ToggleTransparency((TransparencyOption
)(hotkey
- GHK_TOGGLE_TRANSPARENCY
));
348 MarkWholeScreenDirty();
351 case GHK_TOGGLE_INVISIBILITY
:
352 case GHK_TOGGLE_INVISIBILITY
+ 1:
353 case GHK_TOGGLE_INVISIBILITY
+ 2:
354 case GHK_TOGGLE_INVISIBILITY
+ 3:
355 case GHK_TOGGLE_INVISIBILITY
+ 4:
356 case GHK_TOGGLE_INVISIBILITY
+ 5:
357 case GHK_TOGGLE_INVISIBILITY
+ 6:
358 case GHK_TOGGLE_INVISIBILITY
+ 7:
359 /* Invisibility toggle hot keys */
360 ToggleInvisibilityWithTransparency((TransparencyOption
)(hotkey
- GHK_TOGGLE_INVISIBILITY
));
361 MarkWholeScreenDirty();
364 case GHK_TRANSPARENCY_TOOLBAR
:
365 ShowTransparencyToolbar();
368 case GHK_TRANSPARANCY
:
369 ResetRestoreAllTransparency();
372 #ifdef ENABLE_NETWORK
373 case GHK_CHAT
: // smart chat; send to team if any, otherwise to all
375 const NetworkClientInfo
*cio
= NetworkClientInfo::GetByClientID(_network_own_client_id
);
376 if (cio
== NULL
) break;
378 ShowNetworkChatQueryWindow(NetworkClientPreferTeamChat(cio
) ? DESTTYPE_TEAM
: DESTTYPE_BROADCAST
, cio
->client_playas
);
382 case GHK_CHAT_ALL
: // send text message to all clients
383 if (_networking
) ShowNetworkChatQueryWindow(DESTTYPE_BROADCAST
, 0);
386 case GHK_CHAT_COMPANY
: // send text to all team mates
388 const NetworkClientInfo
*cio
= NetworkClientInfo::GetByClientID(_network_own_client_id
);
389 if (cio
== NULL
) break;
391 ShowNetworkChatQueryWindow(DESTTYPE_TEAM
, cio
->client_playas
);
395 case GHK_CHAT_SERVER
: // send text to the server
396 if (_networking
&& !_network_server
) {
397 ShowNetworkChatQueryWindow(DESTTYPE_CLIENT
, CLIENT_ID_SERVER
);
402 default: return false;
407 virtual void OnScroll(Point delta
)
409 this->viewport
->scrollpos_x
+= ScaleByZoom(delta
.x
, this->viewport
->zoom
);
410 this->viewport
->scrollpos_y
+= ScaleByZoom(delta
.y
, this->viewport
->zoom
);
411 this->viewport
->dest_scrollpos_x
= this->viewport
->scrollpos_x
;
412 this->viewport
->dest_scrollpos_y
= this->viewport
->scrollpos_y
;
413 this->refresh
= LINKGRAPH_DELAY
;
416 virtual void OnMouseWheel(int wheel
)
418 if (_settings_client
.gui
.scrollwheel_scrolling
== 0) {
419 ZoomInOrOutToCursorWindow(wheel
< 0, this);
423 virtual void OnResize()
425 if (this->viewport
!= NULL
) {
426 NWidgetViewport
*nvp
= this->GetWidget
<NWidgetViewport
>(WID_M_VIEWPORT
);
427 nvp
->UpdateViewportCoordinates(this);
428 this->refresh
= LINKGRAPH_DELAY
;
433 * Some data on this window has become invalid.
434 * @param data Information about the changed data.
435 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
437 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
439 if (!gui_scope
) return;
440 /* Forward the message to the appropriate toolbar (ingame or scenario editor) */
441 InvalidateWindowData(WC_MAIN_TOOLBAR
, 0, data
, true);
444 static HotkeyList hotkeys
;
447 static const Hotkey global_hotkeys
[] = {
448 Hotkey ("quit", GHK_QUIT
, 'Q' | WKC_CTRL
, 'Q' | WKC_META
),
449 Hotkey ("abandon", GHK_ABANDON
, 'W' | WKC_CTRL
, 'W' | WKC_META
),
450 Hotkey ("console", GHK_CONSOLE
, WKC_BACKQUOTE
),
451 Hotkey ("bounding_boxes", GHK_BOUNDING_BOXES
, 'B' | WKC_CTRL
),
452 Hotkey ("dirty_blocks", GHK_DIRTY_BLOCKS
, 'I' | WKC_CTRL
),
453 Hotkey ("center", GHK_CENTER
, 'C'),
454 Hotkey ("center_zoom", GHK_CENTER_ZOOM
, 'Z'),
455 Hotkey ("reset_object_to_place", GHK_RESET_OBJECT_TO_PLACE
, WKC_ESC
),
456 Hotkey ("delete_windows", GHK_DELETE_WINDOWS
, WKC_DELETE
),
457 Hotkey ("delete_all_windows", GHK_DELETE_NONVITAL_WINDOWS
, WKC_DELETE
| WKC_SHIFT
),
458 Hotkey ("refresh_screen", GHK_REFRESH_SCREEN
, 'R' | WKC_CTRL
),
460 Hotkey ("crash_game", GHK_CRASH
, '0' | WKC_ALT
),
461 Hotkey ("money", GHK_MONEY
, '1' | WKC_ALT
),
462 Hotkey ("update_coordinates", GHK_UPDATE_COORDS
, '2' | WKC_ALT
),
464 Hotkey ("transparency_signs", GHK_TOGGLE_TRANSPARENCY
, '1' | WKC_CTRL
),
465 Hotkey ("transparency_trees", GHK_TOGGLE_TRANSPARENCY
+ 1, '2' | WKC_CTRL
),
466 Hotkey ("transparency_houses", GHK_TOGGLE_TRANSPARENCY
+ 2, '3' | WKC_CTRL
),
467 Hotkey ("transparency_industries", GHK_TOGGLE_TRANSPARENCY
+ 3, '4' | WKC_CTRL
),
468 Hotkey ("transparency_buildings", GHK_TOGGLE_TRANSPARENCY
+ 4, '5' | WKC_CTRL
),
469 Hotkey ("transparency_bridges", GHK_TOGGLE_TRANSPARENCY
+ 5, '6' | WKC_CTRL
),
470 Hotkey ("transparency_structures", GHK_TOGGLE_TRANSPARENCY
+ 6, '7' | WKC_CTRL
),
471 Hotkey ("transparency_catenary", GHK_TOGGLE_TRANSPARENCY
+ 7, '8' | WKC_CTRL
),
472 Hotkey ("transparency_loading", GHK_TOGGLE_TRANSPARENCY
+ 8, '9' | WKC_CTRL
),
473 Hotkey ("invisibility_signs", GHK_TOGGLE_INVISIBILITY
, '1' | WKC_CTRL
| WKC_SHIFT
),
474 Hotkey ("invisibility_trees", GHK_TOGGLE_INVISIBILITY
+ 1, '2' | WKC_CTRL
| WKC_SHIFT
),
475 Hotkey ("invisibility_houses", GHK_TOGGLE_INVISIBILITY
+ 2, '3' | WKC_CTRL
| WKC_SHIFT
),
476 Hotkey ("invisibility_industries", GHK_TOGGLE_INVISIBILITY
+ 3, '4' | WKC_CTRL
| WKC_SHIFT
),
477 Hotkey ("invisibility_buildings", GHK_TOGGLE_INVISIBILITY
+ 4, '5' | WKC_CTRL
| WKC_SHIFT
),
478 Hotkey ("invisibility_bridges", GHK_TOGGLE_INVISIBILITY
+ 5, '6' | WKC_CTRL
| WKC_SHIFT
),
479 Hotkey ("invisibility_structures", GHK_TOGGLE_INVISIBILITY
+ 6, '7' | WKC_CTRL
| WKC_SHIFT
),
480 Hotkey ("invisibility_catenary", GHK_TOGGLE_INVISIBILITY
+ 7, '8' | WKC_CTRL
| WKC_SHIFT
),
481 Hotkey ("transparency_toolbar", GHK_TRANSPARENCY_TOOLBAR
, 'X' | WKC_CTRL
),
482 Hotkey ("toggle_transparency", GHK_TRANSPARANCY
, 'X'),
483 #ifdef ENABLE_NETWORK
484 Hotkey ("chat", GHK_CHAT
, WKC_RETURN
, 'T'),
485 Hotkey ("chat_all", GHK_CHAT_ALL
, WKC_RETURN
| WKC_SHIFT
, 'T' | WKC_SHIFT
),
486 Hotkey ("chat_company", GHK_CHAT_COMPANY
, WKC_RETURN
| WKC_CTRL
, 'T' | WKC_CTRL
),
487 Hotkey ("chat_server", GHK_CHAT_SERVER
, WKC_RETURN
| WKC_CTRL
| WKC_SHIFT
, 'T' | WKC_CTRL
| WKC_SHIFT
),
490 HotkeyList
MainWindow::hotkeys("global", global_hotkeys
);
492 static const WindowDesc
_main_window_desc(
494 WC_MAIN_WINDOW
, WC_NONE
,
496 _nested_main_window_widgets
, lengthof(_nested_main_window_widgets
),
497 NULL
, &MainWindow::hotkeys
501 * Does the given keycode match one of the keycodes bound to 'quit game'?
502 * @param keycode The keycode that was pressed by the user.
503 * @return True iff the keycode matches one of the hotkeys for 'quit'.
505 bool IsQuitKey(uint16 keycode
)
507 int num
= MainWindow::hotkeys
.CheckMatch(keycode
);
508 return num
== GHK_QUIT
;
512 void ShowSelectGameWindow();
515 * Initialise the default colours (remaps and the likes), and load the main windows.
517 void SetupColoursAndInitialWindow()
519 for (uint i
= 0; i
!= 16; i
++) {
520 const byte
*b
= GetNonSprite(PALETTE_RECOLOUR_START
+ i
, ST_RECOLOUR
);
523 memcpy(_colour_gradient
[i
], b
+ 0xC6, sizeof(_colour_gradient
[i
]));
526 new MainWindow(&_main_window_desc
);
528 /* XXX: these are not done */
529 switch (_game_mode
) {
530 default: NOT_REACHED();
532 ShowSelectGameWindow();
543 * Show the vital in-game windows.
545 void ShowVitalWindows()
549 /* Status bad only for normal games */
550 if (_game_mode
== GM_EDITOR
) return;
556 * Size of the application screen changed.
557 * Adapt the game screen-size, re-allocate the open windows, and repaint everything
559 void GameSizeChanged()
561 _cur_resolution
.width
= _screen_width
;
562 _cur_resolution
.height
= _screen_height
;
564 RelocateAllWindows (_screen_width
, _screen_height
);
565 MarkWholeScreenDirty();