tron@2186: /* $Id$ */ tron@2186: rubidium@9111: /** @file main_gui.cpp Handling of the main viewport. */ belugas@6201: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@2292: #include "currency.h" tron@1349: #include "spritecache.h" truelight@0: #include "gui.h" rubidium@8107: #include "window_gui.h" rubidium@8107: #include "window_func.h" rubidium@8107: #include "textbuf_gui.h" rubidium@8224: #include "viewport_func.h" rubidium@8116: #include "command_func.h" rubidium@8976: #include "news_gui.h" rubidium@9336: #include "console_gui.h" truelight@1542: #include "waypoint.h" truelight@4300: #include "genworld.h" peter1138@6427: #include "transparency_gui.h" rubidium@8140: #include "date_func.h" rubidium@8131: #include "functions.h" rubidium@8157: #include "sound_func.h" belugas@7849: #include "transparency.h" rubidium@8114: #include "strings_func.h" rubidium@8123: #include "zoom_func.h" rubidium@8214: #include "string_func.h" rubidium@8254: #include "player_base.h" rubidium@8254: #include "player_func.h" rubidium@8254: #include "player_gui.h" rubidium@8270: #include "settings_type.h" rubidium@8723: #include "toolbar_gui.h" rubidium@9248: #include "statusbar_gui.h" rubidium@8975: #include "variables.h" rubidium@9127: #include "tilehighlight_func.h" truelight@0: rubidium@8264: #include "network/network.h" rubidium@9428: #include "network/network_func.h" rubidium@8264: #include "network/network_gui.h" rubidium@8264: rubidium@8264: #include "table/sprites.h" rubidium@8264: #include "table/strings.h" rubidium@8264: Darkvater@5413: static int _rename_id = 1; Darkvater@5413: static int _rename_what = -1; truelight@0: rubidium@6847: void CcGiveMoney(bool success, TileIndex tile, uint32 p1, uint32 p2) rubidium@6847: { rubidium@6898: #ifdef ENABLE_NETWORK rubidium@9413: if (!success || !_settings_game.economy.give_money) return; rubidium@6847: rubidium@6847: char msg[20]; rubidium@6847: /* Inform the player of this action */ rubidium@6847: snprintf(msg, sizeof(msg), "%d", p1); rubidium@6847: rubidium@6847: if (!_network_server) { rubidium@9428: NetworkClientSendChat(NETWORK_ACTION_GIVE_MONEY, DESTTYPE_TEAM, p2, msg); rubidium@6847: } else { rubidium@9428: NetworkServerSendChat(NETWORK_ACTION_GIVE_MONEY, DESTTYPE_TEAM, p2, msg, NETWORK_SERVER_INDEX); rubidium@6847: } rubidium@6898: #endif /* ENABLE_NETWORK */ rubidium@6847: } rubidium@6847: Darkvater@5431: void HandleOnEditText(const char *str) Darkvater@1799: { Darkvater@5431: int id = _rename_id; Darkvater@5431: _cmd_text = str; truelight@193: Darkvater@1799: switch (_rename_what) { belugas@6201: case 1: // Rename a waypoint Darkvater@5431: if (*str == '\0') return; darkvater@395: DoCommandP(0, id, 0, NULL, CMD_RENAME_WAYPOINT | CMD_MSG(STR_CANT_CHANGE_WAYPOINT_NAME)); truelight@0: break; truelight@543: #ifdef ENABLE_NETWORK belugas@6201: case 3: { // Give money, you can only give money in excess of loan celestar@1962: const Player *p = GetPlayer(_current_player); rubidium@7505: Money money = min(p->player_money - p->current_loan, (Money)(atoi(str) / _currency->rate)); Darkvater@1799: skidd13@7922: uint32 money_c = Clamp(ClampToI32(money), 0, 20000000); // Clamp between 20 million and 0 truelight@813: belugas@6201: /* Give 'id' the money, and substract it from ourself */ rubidium@6990: DoCommandP(0, money_c, id, CcGiveMoney, CMD_GIVE_MONEY | CMD_MSG(STR_INSUFFICIENT_FUNDS)); rubidium@6492: } break; Darkvater@5413: #endif /* ENABLE_NETWORK */ Darkvater@5413: default: NOT_REACHED(); truelight@543: } Darkvater@5413: Darkvater@5413: _rename_id = _rename_what = -1; truelight@0: } truelight@0: dominik@1070: /** dominik@1070: * This code is shared for the majority of the pushbuttons. dominik@1070: * Handles e.g. the pressing of a button (to build things), playing of click sound and sets certain parameters dominik@1070: * dominik@1070: * @param w Window which called the function dominik@1070: * @param widget ID of the widget (=button) that called this function dominik@1070: * @param cursor How should the cursor image change? E.g. cursor with depot image in it dominik@1070: * @param mode Tile highlighting mode, e.g. drawing a rectangle or a dot on the ground dominik@1070: * @param placeproc Procedure which will be called when someone clicks on the map dominik@1070: * @return true if the button is clicked, false if it's unclicked dominik@1070: */ rubidium@7889: bool HandlePlacePushButton(Window *w, int widget, CursorID cursor, ViewportHighlightMode mode, PlaceProc *placeproc) truelight@0: { rubidium@7997: if (w->IsWidgetDisabled(widget)) return false; truelight@0: tron@2621: SndPlayFx(SND_15_BEEP); rubidium@9116: w->SetDirty(); truelight@0: rubidium@7997: if (w->IsWidgetLowered(widget)) { truelight@0: ResetObjectToPlace(); truelight@0: return false; truelight@0: } truelight@0: peter1138@5668: SetObjectToPlace(cursor, PAL_NONE, mode, w->window_class, w->window_number); rubidium@7997: w->LowerWidget(widget); truelight@0: _place_proc = placeproc; truelight@0: return true; truelight@0: } truelight@0: truelight@0: tron@1977: void CcPlaySound10(bool success, TileIndex tile, uint32 p1, uint32 p2) truelight@0: { tron@541: if (success) SndPlayTileFx(SND_12_EXPLOSION, tile); truelight@0: } truelight@0: truelight@543: #ifdef ENABLE_NETWORK Darkvater@4830: void ShowNetworkGiveMoneyWindow(PlayerID player) truelight@543: { truelight@543: _rename_id = player; truelight@543: _rename_what = 3; Darkvater@5431: ShowQueryString(STR_EMPTY, STR_NETWORK_GIVE_MONEY_CAPTION, 30, 180, NULL, CS_NUMERAL); truelight@543: } truelight@543: #endif /* ENABLE_NETWORK */ truelight@543: tron@2116: void ShowRenameWaypointWindow(const Waypoint *wp) truelight@0: { truelight@1542: int id = wp->index; truelight@697: truelight@697: /* Are we allowed to change the name of the waypoint? */ truelight@1542: if (!CheckTileOwnership(wp->xy)) { tron@926: ShowErrorMessage(_error_message, STR_CANT_CHANGE_WAYPOINT_NAME, celestar@3422: TileX(wp->xy) * TILE_SIZE, TileY(wp->xy) * TILE_SIZE); truelight@697: return; truelight@697: } truelight@697: truelight@0: _rename_id = id; truelight@0: _rename_what = 1; tron@534: SetDParam(0, id); Darkvater@5431: ShowQueryString(STR_WAYPOINT_RAW, STR_EDIT_WAYPOINT_NAME, 30, 180, NULL, CS_ALPHANUMERAL); truelight@0: } truelight@0: truelight@0: darkvater@152: /* Zooms a viewport in a window in or out */ darkvater@152: /* No button handling or what so ever */ darkvater@152: bool DoZoomInOutWindow(int how, Window *w) truelight@0: { truelight@0: ViewPort *vp; Darkvater@5045: Darkvater@5045: assert(w != NULL); truelight@0: vp = w->viewport; truelight@0: Darkvater@5044: switch (how) { Darkvater@5044: case ZOOM_IN: truelight@6626: if (vp->zoom == ZOOM_LVL_MIN) return false; smatz@8095: vp->zoom = (ZoomLevel)((int)vp->zoom - 1); Darkvater@5044: vp->virtual_width >>= 1; Darkvater@5044: vp->virtual_height >>= 1; Darkvater@5044: glx@9184: w->viewport->scrollpos_x += vp->virtual_width >> 1; glx@9184: w->viewport->scrollpos_y += vp->virtual_height >> 1; glx@9184: w->viewport->dest_scrollpos_x = w->viewport->scrollpos_x; glx@9184: w->viewport->dest_scrollpos_y = w->viewport->scrollpos_y; Darkvater@5044: break; Darkvater@5044: case ZOOM_OUT: truelight@6626: if (vp->zoom == ZOOM_LVL_MAX) return false; smatz@8095: vp->zoom = (ZoomLevel)((int)vp->zoom + 1); Darkvater@5044: glx@9184: w->viewport->scrollpos_x -= vp->virtual_width >> 1; glx@9184: w->viewport->scrollpos_y -= vp->virtual_height >> 1; glx@9184: w->viewport->dest_scrollpos_x = w->viewport->scrollpos_x; glx@9184: w->viewport->dest_scrollpos_y = w->viewport->scrollpos_y; Darkvater@5044: Darkvater@5044: vp->virtual_width <<= 1; Darkvater@5044: vp->virtual_height <<= 1; Darkvater@5044: break; truelight@0: } KUDr@5214: if (vp != NULL) { // the vp can be null when how == ZOOM_NONE glx@9184: vp->virtual_left = w->viewport->scrollpos_x; glx@9184: vp->virtual_top = w->viewport->scrollpos_y; KUDr@5214: } rubidium@9116: w->SetDirty(); Darkvater@5045: /* Update the windows that have zoom-buttons to perhaps disable their buttons */ rubidium@9165: InvalidateThisWindowData(w); truelight@0: return true; truelight@0: } truelight@0: truelight@193: void ZoomInOrOutToCursorWindow(bool in, Window *w) truelight@0: { rubidium@8723: assert(w != NULL); truelight@0: rubidium@4536: if (_game_mode != GM_MENU) { rubidium@8723: ViewPort *vp = w->viewport; truelight@6626: if ((in && vp->zoom == ZOOM_LVL_MIN) || (!in && vp->zoom == ZOOM_LVL_MAX)) truelight@0: return; truelight@0: rubidium@8723: Point pt = GetTileZoomCenterWindow(in,w); truelight@0: if (pt.x != -1) { peter1138@6730: ScrollWindowTo(pt.x, pt.y, w, true); truelight@0: darkvater@152: DoZoomInOutWindow(in ? ZOOM_IN : ZOOM_OUT, w); truelight@0: } truelight@0: } truelight@0: } truelight@0: rubidium@6247: extern void UpdateAllStationVirtCoord(); truelight@0: rubidium@9205: struct MainWindow : Window tron@4077: { rubidium@9301: MainWindow(int width, int height) : Window(0, 0, width, height, WC_MAIN_WINDOW, NULL) rubidium@9205: { rubidium@9205: InitializeWindowViewport(this, 0, 0, width, height, TileXY(32, 32), ZOOM_LVL_VIEWPORT); rubidium@9205: } rubidium@7643: rubidium@9205: virtual void OnPaint() rubidium@9205: { rubidium@9273: this->DrawViewport(); rubidium@9205: if (_game_mode == GM_MENU) { rubidium@9205: int off_x = _screen.width / 2; truelight@0: rubidium@9205: DrawSprite(SPR_OTTD_O, PAL_NONE, off_x - 120, 50); rubidium@9205: DrawSprite(SPR_OTTD_P, PAL_NONE, off_x - 86, 50); rubidium@9205: DrawSprite(SPR_OTTD_E, PAL_NONE, off_x - 53, 50); rubidium@9205: DrawSprite(SPR_OTTD_N, PAL_NONE, off_x - 22, 50); rubidium@8977: rubidium@9205: DrawSprite(SPR_OTTD_T, PAL_NONE, off_x + 34, 50); rubidium@9205: DrawSprite(SPR_OTTD_T, PAL_NONE, off_x + 65, 50); rubidium@9205: DrawSprite(SPR_OTTD_D, PAL_NONE, off_x + 96, 50); rubidium@9205: } rubidium@9205: } tron@2639: rubidium@9285: virtual EventState OnKeyPress(uint16 key, uint16 keycode) rubidium@9205: { rubidium@9205: switch (keycode) { rubidium@9205: case 'Q' | WKC_CTRL: rubidium@9205: case 'Q' | WKC_META: rubidium@9205: HandleExitGameRequest(); rubidium@9285: return ES_HANDLED; rubidium@9205: } rubidium@9205: rubidium@9205: /* Disable all key shortcuts, except quit shortcuts when rubidium@9205: * generating the world, otherwise they create threading rubidium@9205: * problem during the generating, resulting in random rubidium@9205: * assertions that are hard to trigger and debug */ rubidium@9285: if (IsGeneratingWorld()) return ES_NOT_HANDLED; rubidium@9205: rubidium@9205: if (keycode == WKC_BACKQUOTE) { rubidium@9205: IConsoleSwitch(); rubidium@9285: return ES_HANDLED; rubidium@9205: } rubidium@9205: rubidium@9205: if (keycode == ('B' | WKC_CTRL)) { rubidium@9205: extern bool _draw_bounding_boxes; rubidium@9205: _draw_bounding_boxes = !_draw_bounding_boxes; rubidium@9205: MarkWholeScreenDirty(); rubidium@9285: return ES_HANDLED; rubidium@9205: } rubidium@9205: rubidium@9285: if (_game_mode == GM_MENU) return ES_NOT_HANDLED; rubidium@9205: rubidium@9205: switch (keycode) { rubidium@9205: case 'C': rubidium@9205: case 'Z': { rubidium@9205: Point pt = GetTileBelowCursor(); rubidium@9205: if (pt.x != -1) { rubidium@9205: if (keycode == 'Z') MaxZoomInOut(ZOOM_IN, this); rubidium@9205: ScrollMainWindowTo(pt.x, pt.y); rubidium@9205: } smatz@8806: break; rubidium@8977: } smatz@8806: rubidium@9205: case WKC_ESC: ResetObjectToPlace(); break; rubidium@9205: case WKC_DELETE: DeleteNonVitalWindows(); break; rubidium@9205: case WKC_DELETE | WKC_SHIFT: DeleteAllNonVitalWindows(); break; rubidium@9205: case 'R' | WKC_CTRL: MarkWholeScreenDirty(); break; rubidium@8977: rubidium@8977: #if defined(_DEBUG) rubidium@9205: case '0' | WKC_ALT: // Crash the game rubidium@9205: *(byte*)0 = 0; rubidium@9205: break; rubidium@8977: rubidium@9205: case '1' | WKC_ALT: // Gimme money rubidium@9205: /* Server can not cheat in advertise mode either! */ rubidium@9481: if (!_networking || !_network_server || !_settings_client.network.server_advertise) rubidium@9205: DoCommandP(0, 10000000, 0, NULL, CMD_MONEY_CHEAT); rubidium@9205: break; rubidium@8977: rubidium@9205: case '2' | WKC_ALT: // Update the coordinates of all station signs rubidium@9205: UpdateAllStationVirtCoord(); rubidium@9205: break; rubidium@8977: #endif rubidium@8977: rubidium@9205: case '1' | WKC_CTRL: rubidium@9205: case '2' | WKC_CTRL: rubidium@9205: case '3' | WKC_CTRL: rubidium@9205: case '4' | WKC_CTRL: rubidium@9205: case '5' | WKC_CTRL: rubidium@9205: case '6' | WKC_CTRL: rubidium@9205: case '7' | WKC_CTRL: rubidium@9205: case '8' | WKC_CTRL: rubidium@9205: case '9' | WKC_CTRL: rubidium@9205: /* Transparency toggle hot keys */ rubidium@9205: ToggleTransparency((TransparencyOption)(keycode - ('1' | WKC_CTRL))); rubidium@9205: MarkWholeScreenDirty(); rubidium@9205: break; rubidium@8977: rubidium@9205: case '1' | WKC_CTRL | WKC_SHIFT: rubidium@9205: case '2' | WKC_CTRL | WKC_SHIFT: rubidium@9205: case '3' | WKC_CTRL | WKC_SHIFT: rubidium@9205: case '4' | WKC_CTRL | WKC_SHIFT: rubidium@9205: case '5' | WKC_CTRL | WKC_SHIFT: rubidium@9205: case '6' | WKC_CTRL | WKC_SHIFT: rubidium@9205: case '7' | WKC_CTRL | WKC_SHIFT: rubidium@9205: case '8' | WKC_CTRL | WKC_SHIFT: rubidium@9205: /* Invisibility toggle hot keys */ rubidium@9205: ToggleInvisibilityWithTransparency((TransparencyOption)(keycode - ('1' | WKC_CTRL | WKC_SHIFT))); rubidium@9205: MarkWholeScreenDirty(); rubidium@9205: break; rubidium@8977: rubidium@9205: case 'X' | WKC_CTRL: rubidium@9205: ShowTransparencyToolbar(); rubidium@9205: break; rubidium@8977: rubidium@9205: case 'X': rubidium@9205: ResetRestoreAllTransparency(); rubidium@9205: break; peter1138@6427: truelight@543: #ifdef ENABLE_NETWORK rubidium@9205: case WKC_RETURN: case 'T': // smart chat; send to team if any, otherwise to all rubidium@9205: if (_networking) { rubidium@9205: const NetworkClientInfo *cio = NetworkFindClientInfoFromIndex(_network_own_client_index); rubidium@9205: bool teamchat = false; rubidium@8977: rubidium@9205: if (cio == NULL) break; rubidium@9205: rubidium@9205: /* Only players actually playing can speak to team. Eg spectators cannot */ rubidium@9652: if (_settings_client.gui.prefer_teamchat && IsValidPlayerID(cio->client_playas)) { rubidium@9205: const NetworkClientInfo *ci; rubidium@9205: FOR_ALL_ACTIVE_CLIENT_INFOS(ci) { rubidium@9205: if (ci->client_playas == cio->client_playas && ci != cio) { rubidium@9205: teamchat = true; rubidium@9205: break; Darkvater@5107: } Darkvater@5107: } rubidium@8977: } Darkvater@4887: rubidium@9205: ShowNetworkChatQueryWindow(teamchat ? DESTTYPE_TEAM : DESTTYPE_BROADCAST, cio->client_playas); rubidium@9205: } rubidium@9205: break; Darkvater@5672: rubidium@9205: case WKC_SHIFT | WKC_RETURN: case WKC_SHIFT | 'T': // send text message to all players rubidium@9205: if (_networking) ShowNetworkChatQueryWindow(DESTTYPE_BROADCAST, 0); rubidium@9205: break; rubidium@8977: rubidium@9205: case WKC_CTRL | WKC_RETURN: case WKC_CTRL | 'T': // send text to all team mates rubidium@9205: if (_networking) { rubidium@9205: const NetworkClientInfo *cio = NetworkFindClientInfoFromIndex(_network_own_client_index); rubidium@9205: if (cio == NULL) break; rubidium@9205: rubidium@9205: ShowNetworkChatQueryWindow(DESTTYPE_TEAM, cio->client_playas); rubidium@9205: } rubidium@9205: break; Darkvater@1772: #endif Darkvater@1772: rubidium@9285: default: return ES_NOT_HANDLED; rubidium@9205: } rubidium@9285: return ES_HANDLED; rubidium@9205: } truelight@4335: rubidium@9205: virtual void OnScroll(Point delta) rubidium@9205: { rubidium@9205: ViewPort *vp = IsPtInWindowViewport(this, _cursor.pos.x, _cursor.pos.y); truelight@4337: rubidium@9205: if (vp == NULL) { rubidium@9205: _cursor.fix_at = false; rubidium@9205: _scrolling_viewport = false; rubidium@9205: } Darkvater@5045: rubidium@9205: this->viewport->scrollpos_x += ScaleByZoom(delta.x, vp->zoom); rubidium@9205: this->viewport->scrollpos_y += ScaleByZoom(delta.y, vp->zoom); rubidium@9205: this->viewport->dest_scrollpos_x = this->viewport->scrollpos_x; rubidium@9205: this->viewport->dest_scrollpos_y = this->viewport->scrollpos_y; rubidium@9205: }; rubidium@9205: rubidium@9205: virtual void OnMouseWheel(int wheel) rubidium@9205: { rubidium@9205: ZoomInOrOutToCursorWindow(wheel < 0, this); truelight@0: } rubidium@9205: rubidium@9205: virtual void OnInvalidateData(int data) rubidium@9205: { rubidium@9205: /* Forward the message to the appropiate toolbar (ingame or scenario editor) */ rubidium@9205: InvalidateWindowData(WC_MAIN_TOOLBAR, 0, data); rubidium@9205: } rubidium@9205: }; truelight@0: truelight@0: rubidium@6247: void ShowSelectGameWindow(); rubidium@6247: rubidium@6247: void SetupColorsAndInitialWindow() truelight@0: { rubidium@8977: for (uint i = 0; i != 16; i++) { belugas@4171: const byte *b = GetNonSprite(PALETTE_RECOLOR_START + i); tron@1357: truelight@0: assert(b); tron@4444: memcpy(_colour_gradient[i], b + 0xC6, sizeof(_colour_gradient[i])); truelight@0: } truelight@0: rubidium@9205: new MainWindow(_screen.width, _screen.height); Darkvater@5432: belugas@6201: /* XXX: these are not done */ tron@2639: switch (_game_mode) { Darkvater@5432: default: NOT_REACHED(); Darkvater@5432: case GM_MENU: Darkvater@5432: ShowSelectGameWindow(); Darkvater@5432: break; truelight@0: Darkvater@5432: case GM_NORMAL: Darkvater@5432: case GM_EDITOR: Darkvater@5432: ShowVitalWindows(); Darkvater@5432: break; truelight@0: } truelight@0: } truelight@0: rubidium@6247: void ShowVitalWindows() darkvater@983: { rubidium@9240: AllocateToolbar(); Darkvater@5048: truelight@4300: /* Status bad only for normal games */ truelight@4300: if (_game_mode == GM_EDITOR) return; darkvater@983: rubidium@8976: ShowStatusBar(); darkvater@983: } darkvater@983: rubidium@8857: /** rubidium@8857: * Size of the application screen changed. rubidium@8857: * Adapt the game screen-size, re-allocate the open windows, and repaint everything rubidium@8857: */ rubidium@6247: void GameSizeChanged() truelight@0: { smatz@9533: _cur_resolution.width = _screen.width; smatz@9533: _cur_resolution.height = _screen.height; rubidium@8985: ScreenSizeChanged(); truelight@0: RelocateAllWindows(_screen.width, _screen.height); truelight@0: MarkWholeScreenDirty(); truelight@0: }