• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Return-To-The-Roots / s25client / 24635933460

19 Apr 2026 06:21PM UTC coverage: 50.203% (-0.2%) from 50.362%
24635933460

Pull #1890

github

web-flow
Merge 3877a723b into 7b5704a17
Pull Request #1890: Add support for borderless Windows

167 of 641 new or added lines in 44 files covered. (26.05%)

26 existing lines in 9 files now uncovered.

23078 of 45969 relevant lines covered (50.2%)

43616.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

35.36
/libs/s25main/desktops/dskGameInterface.cpp
1
// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "dskGameInterface.h"
6
#include "CollisionDetection.h"
7
#include "EventManager.h"
8
#include "Game.h"
9
#include "GamePlayer.h"
10
#include "Loader.h"
11
#include "NWFInfo.h"
12
#include "Settings.h"
13
#include "SoundManager.h"
14
#include "WindowManager.h"
15
#include "addons/AddonMaxWaterwayLength.h"
16
#include "buildings/noBuildingSite.h"
17
#include "buildings/nobHQ.h"
18
#include "buildings/nobHarborBuilding.h"
19
#include "buildings/nobMilitary.h"
20
#include "buildings/nobStorehouse.h"
21
#include "buildings/nobTemple.h"
22
#include "buildings/nobUsual.h"
23
#include "controls/ctrlImageButton.h"
24
#include "controls/ctrlText.h"
25
#include "driver/MouseCoords.h"
26
#include "drivers/VideoDriverWrapper.h"
27
#include "helpers/format.hpp"
28
#include "helpers/strUtils.h"
29
#include "helpers/toString.h"
30
#include "ingameWindows/iwAIDebug.h"
31
#include "ingameWindows/iwAction.h"
32
#include "ingameWindows/iwBaseWarehouse.h"
33
#include "ingameWindows/iwBuildOrder.h"
34
#include "ingameWindows/iwBuilding.h"
35
#include "ingameWindows/iwBuildingProductivities.h"
36
#include "ingameWindows/iwBuildingSite.h"
37
#include "ingameWindows/iwBuildings.h"
38
#include "ingameWindows/iwDiplomacy.h"
39
#include "ingameWindows/iwDistribution.h"
40
#include "ingameWindows/iwEconomicProgress.h"
41
#include "ingameWindows/iwEndgame.h"
42
#include "ingameWindows/iwHQ.h"
43
#include "ingameWindows/iwHarborBuilding.h"
44
#include "ingameWindows/iwInventory.h"
45
#include "ingameWindows/iwMainMenu.h"
46
#include "ingameWindows/iwMapDebug.h"
47
#include "ingameWindows/iwMerchandiseStatistics.h"
48
#include "ingameWindows/iwMilitary.h"
49
#include "ingameWindows/iwMilitaryBuilding.h"
50
#include "ingameWindows/iwMinimap.h"
51
#include "ingameWindows/iwMusicPlayer.h"
52
#include "ingameWindows/iwOptionsWindow.h"
53
#include "ingameWindows/iwPostWindow.h"
54
#include "ingameWindows/iwRoadWindow.h"
55
#include "ingameWindows/iwSave.h"
56
#include "ingameWindows/iwSettings.h"
57
#include "ingameWindows/iwShip.h"
58
#include "ingameWindows/iwSkipGFs.h"
59
#include "ingameWindows/iwStatistics.h"
60
#include "ingameWindows/iwTempleBuilding.h"
61
#include "ingameWindows/iwTextfile.h"
62
#include "ingameWindows/iwTools.h"
63
#include "ingameWindows/iwTrade.h"
64
#include "ingameWindows/iwTransport.h"
65
#include "ingameWindows/iwVictory.h"
66
#include "lua/GameDataLoader.h"
67
#include "network/GameClient.h"
68
#include "notifications/BuildingNote.h"
69
#include "notifications/NotificationManager.h"
70
#include "ogl/FontStyle.h"
71
#include "ogl/SoundEffectItem.h"
72
#include "ogl/glArchivItem_Bitmap_Player.h"
73
#include "ogl/glFont.h"
74
#include "pathfinding/FindPathForRoad.h"
75
#include "postSystem/PostBox.h"
76
#include "postSystem/PostMsg.h"
77
#include "random/Random.h"
78
#include "world/GameWorldBase.h"
79
#include "world/GameWorldViewer.h"
80
#include "nodeObjs/noFlag.h"
81
#include "nodeObjs/noTree.h"
82
#include "gameData/BuildingProperties.h"
83
#include "gameData/GameConsts.h"
84
#include "gameData/GuiConsts.h"
85
#include "gameData/TerrainDesc.h"
86
#include "gameData/const_gui_ids.h"
87
#include "liblobby/LobbyClient.h"
88
#include <algorithm>
89
#include <cstdio>
90
#include <utility>
91

92
namespace {
93
enum
94
{
95
    ID_btMap,
96
    ID_btOptions,
97
    ID_btConstructionAid,
98
    ID_btPost,
99
    ID_txtNumMsg
100
};
101

102
/// Size of buttons on lower bar
103
constexpr Extent btSize = Extent(37, 32);
104
/// Offsets of the buttons relative to the "border" graphics on the lower bar
105
constexpr DrawPoint btOffset(44, 4);
106

107
float getNextZoomLevel(const float currentZoom)
7✔
108
{
109
    // Get first level bigger than current zoom
110
    // NOLINTNEXTLINE(readability-qualified-auto)
111
    auto it = std::upper_bound(ZOOM_FACTORS.begin(), ZOOM_FACTORS.end(), currentZoom);
7✔
112
    return (it == ZOOM_FACTORS.end()) ? ZOOM_FACTORS.front() : *it;
7✔
113
}
114

115
float getPreviousZoomLevel(const float currentZoom)
4✔
116
{
117
    // Get last level bigger or equal than current zoom
118
    // NOLINTNEXTLINE(readability-qualified-auto)
119
    auto it = std::lower_bound(ZOOM_FACTORS.begin(), ZOOM_FACTORS.end(), currentZoom);
4✔
120
    return (it == ZOOM_FACTORS.begin()) ? ZOOM_FACTORS.back() : *(--it);
4✔
121
}
122
} // namespace
123

124
dskGameInterface::dskGameInterface(std::shared_ptr<Game> game, std::shared_ptr<const NWFInfo> nwfInfo,
6✔
125
                                   unsigned playerIdx, bool initOGL)
6✔
126
    : Desktop(nullptr), game_(std::move(game)), nwfInfo_(std::move(nwfInfo)),
12✔
127
      worldViewer(playerIdx, const_cast<Game&>(*game_).world_),
6✔
128
      gwv(worldViewer, Position(0, 0), VIDEODRIVER.GetRenderSize()), cbb(*LOADER.GetPaletteN("pal5")),
18✔
129
      actionwindow(nullptr), roadwindow(nullptr), minimap(worldViewer), isScrolling(false),
6✔
130
      cheats_(const_cast<Game&>(*game_).world_, GAMECLIENT), cheatCommandTracker_(cheats_)
18✔
131
{
132
    road.mode = RoadBuildMode::Disabled;
6✔
133
    road.point = MapPoint(0, 0);
6✔
134
    road.start = MapPoint(0, 0);
6✔
135

136
    SetScale(false);
6✔
137

138
    const glArchivItem_Bitmap& imgButtonBar = *LOADER.GetImageN("resource", 29);
12✔
139

140
    auto barPos =
141
      DrawPoint((GetSize().x - imgButtonBar.getWidth()) / 2, GetSize().y - imgButtonBar.getHeight()) + btOffset;
6✔
142

143
    AddImageButton(ID_btMap, barPos, btSize, TextureColor::Green1, LOADER.GetImageN("io", 50), _("Map"))
6✔
144
      ->SetBorder(false);
12✔
145
    barPos.x += btSize.x;
6✔
146
    AddImageButton(ID_btOptions, barPos, btSize, TextureColor::Green1, LOADER.GetImageN("io", 192), _("Main selection"))
6✔
147
      ->SetBorder(false);
12✔
148
    barPos.x += btSize.x;
6✔
149
    AddImageButton(ID_btConstructionAid, barPos, btSize, TextureColor::Green1, LOADER.GetImageN("io", 83),
6✔
150
                   _("Construction aid mode"))
151
      ->SetBorder(false);
12✔
152
    barPos.x += btSize.x;
6✔
153
    AddImageButton(ID_btPost, barPos, btSize, TextureColor::Green1, LOADER.GetImageN("io", 62), _("Post office"))
6✔
154
      ->SetBorder(false);
12✔
155
    barPos += DrawPoint(18, 24);
6✔
156

157
    AddText(ID_txtNumMsg, barPos, "", COLOR_YELLOW, FontStyle::CENTER | FontStyle::VCENTER, SmallFont);
6✔
158

159
    const_cast<Game&>(*game_).world_.SetGameInterface(this);
6✔
160

161
    std::fill(borders.begin(), borders.end(), (glArchivItem_Bitmap*)(nullptr));
6✔
162
    cbb.loadEdges(LOADER.GetArchive("resource"));
12✔
163
    cbb.buildBorder(VIDEODRIVER.GetRenderSize(), borders);
6✔
164

165
    InitPlayer();
6✔
166
    if(initOGL)
6✔
167
        worldViewer.InitTerrainRenderer();
×
168

169
    VIDEODRIVER.setTargetFramerate(SETTINGS.video.framerate); // Use requested setting for ingame
6✔
170
}
6✔
171

172
void dskGameInterface::InitPlayer()
6✔
173
{
174
    // Jump to players HQ if it exists
175
    if(worldViewer.GetPlayer().GetHQPos().isValid())
6✔
176
        gwv.MoveToMapPt(worldViewer.GetPlayer().GetHQPos());
6✔
177

178
    evBld = worldViewer.GetWorld().GetNotifications().subscribe<BuildingNote>([this](const auto& note) {
12✔
179
        if(note.player == worldViewer.GetPlayerId())
×
180
            this->OnBuildingNote(note);
×
181
    });
6✔
182
    PostBox& postBox = GetPostBox();
6✔
183
    postBox.ObserveNewMsg([this](const auto& msg, auto msgCt) { this->NewPostMessage(msg, msgCt); });
6✔
184
    postBox.ObserveDeletedMsg([this](auto msgCt) { this->PostMessageDeleted(msgCt); });
6✔
185
    UpdatePostIcon(postBox.GetNumMsgs(), true);
6✔
186
}
6✔
187

188
PostBox& dskGameInterface::GetPostBox()
6✔
189
{
190
    PostBox* postBox = worldViewer.GetWorld().GetPostMgr().GetPostBox(worldViewer.GetPlayerId());
6✔
191
    if(!postBox)
6✔
192
        postBox = &worldViewer.GetWorldNonConst().GetPostMgr().AddPostBox(worldViewer.GetPlayerId());
6✔
193
    RTTR_Assert(postBox != nullptr);
6✔
194
    return *postBox;
6✔
195
}
196

197
dskGameInterface::~dskGameInterface()
6✔
198
{
199
    for(auto& border : borders)
30✔
200
        deletePtr(border);
24✔
201
    GAMECLIENT.RemoveInterface(this);
6✔
202
    LOBBYCLIENT.RemoveListener(this);
6✔
203
}
6✔
204

205
void dskGameInterface::SetActive(bool activate)
10✔
206
{
207
    if(activate == IsActive())
10✔
208
        return;
1✔
209
    if(!activate && isScrolling)
9✔
210
    {
211
        // Stay active if scrolling and no modal window is open
212
        const IngameWindow* wnd = WINDOWMANAGER.GetTopMostWindow();
1✔
213
        if(wnd && wnd->IsModal())
1✔
214
            StopScrolling();
×
215
        else
216
            return;
1✔
217
    }
218
    Desktop::SetActive(activate);
8✔
219
    // Do this here to allow previous screen to keep control
220
    if(activate)
8✔
221
    {
222
        GAMECLIENT.SetInterface(this);
6✔
223
        LOBBYCLIENT.AddListener(this);
6✔
224
        if(!game_->IsStarted())
6✔
225
        {
226
            GAMECLIENT.OnGameStart();
6✔
227

228
            ShowPersistentWindowsAfterSwitch();
6✔
229
        }
230
    }
231
}
232

233
void dskGameInterface::StopScrolling()
7✔
234
{
235
    isScrolling = false;
7✔
236
    WINDOWMANAGER.SetCursor(road.mode == RoadBuildMode::Disabled ? Cursor::Hand : Cursor::Remove);
7✔
237
}
7✔
238

239
void dskGameInterface::StartScrolling(const Position& mousePos)
7✔
240
{
241
    startScrollPt = mousePos;
7✔
242
    isScrolling = true;
7✔
243
    WINDOWMANAGER.SetCursor(Cursor::Scroll);
7✔
244
}
7✔
245

246
void dskGameInterface::ToggleFoW()
×
247
{
248
    DisableFoW(!GAMECLIENT.IsReplayFOWDisabled());
×
249
}
×
250

251
void dskGameInterface::DisableFoW(const bool hideFOW)
×
252
{
253
    GAMECLIENT.SetReplayFOW(hideFOW);
×
254
    // Notify viewer and minimap to recalculate the visibility
255
    worldViewer.RecalcAllColors();
×
256
    minimap.UpdateAll();
×
257
}
×
258

259
void dskGameInterface::ShowPersistentWindowsAfterSwitch()
6✔
260
{
261
    auto& windows = SETTINGS.windows.persistentSettings;
6✔
262

263
    if(windows[CGI_CHAT].isOpen)
6✔
264
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwChat>(this));
×
265
    if(windows[CGI_POSTOFFICE].isOpen)
6✔
266
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwPostWindow>(gwv, GetPostBox()));
×
267
    if(windows[CGI_DISTRIBUTION].isOpen)
6✔
268
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwDistribution>(gwv.GetViewer(), GAMECLIENT));
×
269
    if(windows[CGI_BUILDORDER].isOpen && gwv.GetWorld().GetGGS().isEnabled(AddonId::CUSTOM_BUILD_SEQUENCE))
6✔
270
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwBuildOrder>(gwv.GetViewer()));
×
271
    if(windows[CGI_TRANSPORT].isOpen)
6✔
272
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwTransport>(gwv.GetViewer(), GAMECLIENT));
×
273
    if(windows[CGI_MILITARY].isOpen)
6✔
274
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwMilitary>(gwv.GetViewer(), GAMECLIENT));
×
275
    if(windows[CGI_TOOLS].isOpen)
6✔
276
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwTools>(gwv.GetViewer(), GAMECLIENT));
×
277
    if(windows[CGI_INVENTORY].isOpen)
6✔
278
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwInventory>(gwv.GetViewer().GetPlayer()));
×
279
    if(windows[CGI_MINIMAP].isOpen)
6✔
280
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwMinimap>(minimap, gwv));
×
281
    if(windows[CGI_BUILDINGS].isOpen)
6✔
282
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwBuildings>(gwv, GAMECLIENT));
×
283
    if(windows[CGI_BUILDINGSPRODUCTIVITY].isOpen)
6✔
284
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwBuildingProductivities>(gwv.GetViewer().GetPlayer()));
×
285
    if(windows[CGI_MUSICPLAYER].isOpen)
6✔
286
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwMusicPlayer>());
×
287
    if(windows[CGI_STATISTICS].isOpen)
6✔
288
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwStatistics>(gwv.GetViewer()));
×
289
    if(windows[CGI_ECONOMICPROGRESS].isOpen && gwv.GetWorld().getEconHandler())
6✔
290
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwEconomicProgress>(gwv.GetViewer()));
×
291
    if(windows[CGI_DIPLOMACY].isOpen)
6✔
292
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwDiplomacy>(gwv.GetViewer(), GAMECLIENT));
×
293
    if(windows[CGI_SHIP].isOpen)
6✔
294
        WINDOWMANAGER.ShowAfterSwitch(
×
295
          std::make_unique<iwShip>(gwv, GAMECLIENT, gwv.GetViewer().GetPlayer().GetShipByID(0)));
×
296
    if(windows[CGI_MERCHANDISE_STATISTICS].isOpen)
6✔
297
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwMerchandiseStatistics>(gwv.GetViewer().GetPlayer()));
×
298
}
6✔
299

300
void dskGameInterface::Resize(const Extent& newSize)
×
301
{
302
    Window::Resize(newSize);
×
303

304
    // recreate borders
305
    for(auto& border : borders)
×
306
        deletePtr(border);
×
307
    cbb.buildBorder(newSize, borders);
×
308

309
    // move buttons
310
    // Get real renderer size as newSize may get capped but we want to keep the manually drawn borders intact
NEW
311
    const Extent realNewSize = VIDEODRIVER.GetRenderSize();
×
NEW
312
    const glArchivItem_Bitmap& imgButtonBar = *LOADER.GetImageN("resource", 29);
×
313
    DrawPoint barPos =
NEW
314
      DrawPoint((realNewSize.x - imgButtonBar.getWidth()) / 2, realNewSize.y - imgButtonBar.getHeight()) + btOffset;
×
315

316
    auto* button = GetCtrl<ctrlButton>(ID_btMap);
×
317
    button->SetPos(barPos);
×
318

319
    barPos.x += button->GetSize().x;
×
320
    button = GetCtrl<ctrlButton>(ID_btOptions);
×
321
    button->SetPos(barPos);
×
322

323
    barPos.x += button->GetSize().x;
×
324
    button = GetCtrl<ctrlButton>(ID_btConstructionAid);
×
325
    button->SetPos(barPos);
×
326

327
    barPos.x += button->GetSize().x;
×
328
    button = GetCtrl<ctrlButton>(ID_btPost);
×
329
    button->SetPos(barPos);
×
330

331
    barPos += DrawPoint(18, 24);
×
332
    auto* text = GetCtrl<ctrlText>(ID_txtNumMsg);
×
333
    text->SetPos(barPos);
×
334

335
    gwv.Resize(newSize);
×
336
}
×
337

338
void dskGameInterface::Msg_ButtonClick(const unsigned ctrl_id)
×
339
{
340
    switch(ctrl_id)
×
341
    {
342
        case ID_btMap: WINDOWMANAGER.ToggleWindow(std::make_unique<iwMinimap>(minimap, gwv)); break;
×
343
        case ID_btOptions: WINDOWMANAGER.ToggleWindow(std::make_unique<iwMainMenu>(gwv, GAMECLIENT)); break;
×
344
        case ID_btConstructionAid:
×
345
            if(WINDOWMANAGER.IsDesktopActive())
×
346
                gwv.ToggleShowBQ();
×
347
            break;
×
348
        case ID_btPost:
×
349
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwPostWindow>(gwv, GetPostBox()));
×
350
            UpdatePostIcon(GetPostBox().GetNumMsgs(), false);
×
351
            break;
×
352
    }
353
}
×
354

355
void dskGameInterface::Msg_PaintBefore()
×
356
{
357
    Desktop::Msg_PaintBefore();
×
358

359
    // Spiel ausführen
360
    Run();
×
361

362
    /// Padding of the figures
363
    const DrawPoint figPadding(12, 12);
×
364
    const DrawPoint screenSize(VIDEODRIVER.GetRenderSize());
×
365
    // Rahmen zeichnen
366
    borders[0]->DrawFull(DrawPoint(0, 0));                                      // oben (mit Ecken)
×
367
    borders[1]->DrawFull(DrawPoint(0, screenSize.y - figPadding.y));            // unten (mit Ecken)
×
368
    borders[2]->DrawFull(DrawPoint(0, figPadding.y));                           // links
×
369
    borders[3]->DrawFull(DrawPoint(screenSize.x - figPadding.x, figPadding.y)); // rechts
×
370

371
    // The figure/statues and the button bar
372
    glArchivItem_Bitmap& imgFigLeftTop = *LOADER.GetImageN("resource", 17);
×
373
    glArchivItem_Bitmap& imgFigRightTop = *LOADER.GetImageN("resource", 18);
×
374
    glArchivItem_Bitmap& imgFigLeftBot = *LOADER.GetImageN("resource", 19);
×
375
    glArchivItem_Bitmap& imgFigRightBot = *LOADER.GetImageN("resource", 20);
×
376
    imgFigLeftTop.DrawFull(figPadding);
×
377
    imgFigRightTop.DrawFull(DrawPoint(screenSize.x - figPadding.x - imgFigRightTop.getWidth(), figPadding.y));
×
378
    imgFigLeftBot.DrawFull(DrawPoint(figPadding.x, screenSize.y - figPadding.y - imgFigLeftBot.getHeight()));
×
379
    imgFigRightBot.DrawFull(screenSize - figPadding - imgFigRightBot.GetSize());
×
380

381
    glArchivItem_Bitmap& imgButtonBar = *LOADER.GetImageN("resource", 29);
×
382
    imgButtonBar.DrawFull(
×
383
      DrawPoint((screenSize.x - imgButtonBar.getWidth()) / 2, screenSize.y - imgButtonBar.getHeight()));
×
384
}
×
385

386
void dskGameInterface::Msg_PaintAfter()
×
387
{
388
    Desktop::Msg_PaintAfter();
×
389

390
    const GameWorldBase& world = worldViewer.GetWorld();
×
391

392
    if(SETTINGS.global.showGFInfo)
×
393
    {
394
        std::array<char, 256> nwf_string;
395
        if(GAMECLIENT.IsReplayModeOn())
×
396
        {
397
            snprintf(nwf_string.data(), nwf_string.size(),
×
398
                     _("(Replay-Mode) Current GF: %u (End at: %u) / GF length: %u ms / NWF length: %u gf (%u ms)"),
399
                     world.GetEvMgr().GetCurrentGF(), GAMECLIENT.GetLastReplayGF(),
×
400
                     GAMECLIENT.GetGFLength() / FramesInfo::milliseconds32_t(1), GAMECLIENT.GetNWFLength(),
×
401
                     GAMECLIENT.GetNWFLength() * GAMECLIENT.GetGFLength() / FramesInfo::milliseconds32_t(1));
×
402
        } else
403
            snprintf(nwf_string.data(), nwf_string.size(),
×
404
                     _("Current GF: %u / GF length: %u ms / NWF length: %u gf (%u ms) /  Ping: %u ms"),
405
                     world.GetEvMgr().GetCurrentGF(), GAMECLIENT.GetGFLength() / FramesInfo::milliseconds32_t(1),
×
406
                     GAMECLIENT.GetNWFLength(),
×
407
                     GAMECLIENT.GetNWFLength() * GAMECLIENT.GetGFLength() / FramesInfo::milliseconds32_t(1),
×
408
                     worldViewer.GetPlayer().ping);
×
409
        NormalFont->Draw(DrawPoint(30, 1), nwf_string.data(), FontStyle{}, COLOR_YELLOW);
×
410
    }
411

412
    // tournament mode?
413
    const unsigned tournamentDuration = GAMECLIENT.GetTournamentModeDuration();
×
414
    if(tournamentDuration)
×
415
    {
416
        unsigned curGF = world.GetEvMgr().GetCurrentGF();
×
417
        std::string tournamentNotice;
×
418
        if(curGF >= tournamentDuration)
×
419
            tournamentNotice = _("Tournament finished");
×
420
        else
421
        {
422
            tournamentNotice =
423
              helpers::format("Tournament mode: %1% remaining", GAMECLIENT.FormatGFTime(tournamentDuration - curGF));
×
424
        }
425
        NormalFont->Draw(DrawPoint(VIDEODRIVER.GetRenderSize().x - 30, 1), tournamentNotice, FontStyle::AlignH::RIGHT,
×
426
                         COLOR_YELLOW);
427
    }
428

429
    // Replaydateianzeige in der linken unteren Ecke
430
    if(GAMECLIENT.IsReplayModeOn())
×
431
    {
432
        NormalFont->Draw(DrawPoint(0, VIDEODRIVER.GetRenderSize().y), GAMECLIENT.GetReplayFilename().string(),
×
433
                         FontStyle::BOTTOM, COLOR_YELLOW);
434
    } else
435
    {
436
        // Laggende Spieler anzeigen in Form von Schnecken
437
        DrawPoint snailPos(VIDEODRIVER.GetRenderSize().x - 70, 35);
×
438
        for(const NWFPlayerInfo& player : nwfInfo_->getPlayerInfos())
×
439
        {
440
            if(player.isLagging)
×
441
            {
442
                LOADER.GetPlayerImage("rttr", 0)->DrawFull(Rect(snailPos, 30, 30), COLOR_WHITE,
×
443
                                                           game_->world_.GetPlayer(player.id).color);
×
444
                snailPos.x -= 40;
×
445
            }
446
        }
447
    }
448

449
    // Show icons in the upper right corner of the game interface
450
    DrawPoint iconPos(VIDEODRIVER.GetRenderSize().x - 56, 32);
×
451

452
    // Draw cheating indicator icon (WINTER)
453
    if(cheats_.isCheatModeOn())
×
454
    {
455
        glArchivItem_Bitmap* cheatingImg = LOADER.GetImageN("io", 75);
×
456
        cheatingImg->DrawFull(iconPos);
×
457
        iconPos -= DrawPoint(cheatingImg->getWidth() + 6, 0);
×
458
    }
459

460
    // Draw speed indicator icon
461
    const int speedStep = static_cast<int>(REFERENCE_SPEED / 10ms) - static_cast<int>(GAMECLIENT.GetGFLength() / 10ms);
×
462

463
    if(speedStep != 0)
×
464
    {
465
        glArchivItem_Bitmap* runnerImg = LOADER.GetImageN("io", 164);
×
466

467
        runnerImg->DrawFull(iconPos);
×
468

469
        if(speedStep != 1)
×
470
        {
471
            std::string multiplier = helpers::toString(std::abs(speedStep));
×
472
            NormalFont->Draw(iconPos - runnerImg->GetOrigin() + DrawPoint(19, 6), multiplier, FontStyle::LEFT,
×
473
                             speedStep > 0 ? COLOR_YELLOW : COLOR_RED);
474
        }
475
        iconPos -= DrawPoint(runnerImg->getWidth() + 4, 0);
×
476
    }
477

478
    // Draw zoom level indicator icon
479
    if(gwv.GetCurrentTargetZoomFactor() != 1.f) //-V550
×
480
    {
481
        glArchivItem_Bitmap* magnifierImg = LOADER.GetImageN("io", 36);
×
482

483
        magnifierImg->DrawFull(iconPos);
×
484

485
        std::string zoom_percent = helpers::toString((int)(gwv.GetCurrentTargetZoomFactor() * 100)) + "%";
×
486
        NormalFont->Draw(iconPos - magnifierImg->GetOrigin() + DrawPoint(9, 7), zoom_percent, FontStyle::CENTER,
×
487
                         COLOR_YELLOW);
488
        iconPos -= DrawPoint(magnifierImg->getWidth() + 4, 0);
×
489
    }
490
}
×
491

492
bool dskGameInterface::ContextClick(const MouseCoords& mc)
2✔
493
{
494
    // Handle road building mode if active
495
    if(road.mode != RoadBuildMode::Disabled)
2✔
496
    {
497
        // in "richtige" Map-Koordinaten Konvertieren, den aktuellen selektierten Punkt
498
        const MapPoint selPt = gwv.GetSelectedPt();
1✔
499

500
        if(selPt == road.point)
1✔
501
        {
502
            // Selektierter Punkt ist der gleiche wie der Straßenpunkt --> Fenster mit Wegbau abbrechen
503
            ShowRoadWindow(mc.pos);
×
504
        } else
505
        {
506
            // altes Roadwindow schließen
507
            WINDOWMANAGER.Close((unsigned)CGI_ROADWINDOW);
1✔
508

509
            // Ist das ein gültiger neuer Wegpunkt?
510
            if(worldViewer.IsRoadAvailable(road.mode == RoadBuildMode::Boat, selPt)
1✔
511
               && worldViewer.IsPlayerTerritory(selPt))
1✔
512
            {
513
                MapPoint targetPt = selPt;
1✔
514
                if(!BuildRoadPart(targetPt))
1✔
515
                    ShowRoadWindow(mc.pos);
×
516
            } else if(worldViewer.GetBQ(selPt) != BuildingQuality::Nothing)
×
517
            {
518
                // Wurde bereits auf das gebaute Stück geklickt?
519
                unsigned idOnRoad = GetIdInCurBuildRoad(selPt);
×
520
                if(idOnRoad)
×
521
                    DemolishRoad(idOnRoad);
×
522
                else
523
                {
524
                    MapPoint targetPt = selPt;
×
525
                    if(BuildRoadPart(targetPt))
×
526
                    {
527
                        // Ist der Zielpunkt der gleiche geblieben?
528
                        if(selPt == targetPt)
×
529
                            GI_BuildRoad();
×
530
                    } else if(selPt == targetPt)
×
531
                        ShowRoadWindow(mc.pos);
×
532
                }
533
            }
534
            // Wurde auf eine Flagge geklickt und ist diese Flagge nicht der Weganfangspunkt?
535
            else if(worldViewer.GetWorld().GetNO(selPt)->GetType() == NodalObjectType::Flag && selPt != road.start)
×
536
            {
537
                MapPoint targetPt = selPt;
×
538
                if(BuildRoadPart(targetPt))
×
539
                {
540
                    if(selPt == targetPt)
×
541
                        GI_BuildRoad();
×
542
                } else if(selPt == targetPt)
×
543
                    ShowRoadWindow(mc.pos);
×
544
            } else
545
            {
546
                unsigned tbr = GetIdInCurBuildRoad(selPt);
×
547
                // Wurde bereits auf das gebaute Stück geklickt?
548
                if(tbr)
×
549
                    DemolishRoad(tbr);
×
550
                else
551
                    ShowRoadWindow(mc.pos);
×
552
            }
553
        }
554
    } else // Not in road building mode
555
    {
556
        bool enable_military_buildings = false;
1✔
557

558
        iwAction::Tabs action_tabs;
1✔
559

560
        const MapPoint cSel = gwv.GetSelectedPt();
1✔
561

562
        // Vielleicht steht hier auch ein Schiff?
563
        if(const noShip* ship = worldViewer.GetShip(cSel))
1✔
564
        {
565
            WINDOWMANAGER.Show(std::make_unique<iwShip>(gwv, GAMECLIENT, ship));
×
566
            return true;
×
567
        }
568

569
        // Evtl ists nen Haus? (unser Haus)
570
        const noBase& selObj = *worldViewer.GetWorld().GetNO(cSel);
1✔
571
        if(selObj.GetType() == NodalObjectType::Building && worldViewer.IsOwner(cSel))
1✔
572
        {
573
            if(auto* wnd = WINDOWMANAGER.FindNonModalWindow(CGI_BUILDING + MapBase::CreateGUIID(cSel)))
×
574
            {
575
                WINDOWMANAGER.SetActiveWindow(*wnd);
×
576
                return true;
×
577
            }
578
            BuildingType bt = static_cast<const noBuilding&>(selObj).GetBuildingType();
×
579
            // HQ
580
            if(bt == BuildingType::Headquarters)
×
581
                WINDOWMANAGER.Show(
×
582
                  std::make_unique<iwHQ>(gwv, GAMECLIENT, worldViewer.GetWorldNonConst().GetSpecObj<nobHQ>(cSel)));
×
583
            // Lagerhäuser
584
            else if(bt == BuildingType::Storehouse)
×
585
                WINDOWMANAGER.Show(std::make_unique<iwBaseWarehouse>(
×
586
                  gwv, GAMECLIENT, worldViewer.GetWorldNonConst().GetSpecObj<nobStorehouse>(cSel)));
×
587
            // Hafengebäude
588
            else if(bt == BuildingType::HarborBuilding)
×
589
                WINDOWMANAGER.Show(std::make_unique<iwHarborBuilding>(
×
590
                  gwv, GAMECLIENT, worldViewer.GetWorldNonConst().GetSpecObj<nobHarborBuilding>(cSel)));
×
591
            // Militärgebäude
592
            else if(BuildingProperties::IsMilitary(bt))
×
593
                WINDOWMANAGER.Show(std::make_unique<iwMilitaryBuilding>(
×
594
                  gwv, GAMECLIENT, worldViewer.GetWorldNonConst().GetSpecObj<nobMilitary>(cSel)));
×
595
            else if(bt == BuildingType::Temple)
×
596
                WINDOWMANAGER.Show(std::make_unique<iwTempleBuilding>(
×
597
                  gwv, GAMECLIENT, worldViewer.GetWorldNonConst().GetSpecObj<nobTemple>(cSel)));
×
598
            else
599
                WINDOWMANAGER.Show(std::make_unique<iwBuilding>(
×
600
                  gwv, GAMECLIENT, worldViewer.GetWorldNonConst().GetSpecObj<nobUsual>(cSel)));
×
601
            return true;
×
602
        }
603
        // oder vielleicht eine Baustelle?
604
        else if(selObj.GetType() == NodalObjectType::Buildingsite && worldViewer.IsOwner(cSel))
1✔
605
        {
606
            if(!WINDOWMANAGER.FindNonModalWindow(CGI_BUILDING + MapBase::CreateGUIID(cSel)))
×
607
                WINDOWMANAGER.Show(
×
608
                  std::make_unique<iwBuildingSite>(gwv, worldViewer.GetWorld().GetSpecObj<noBuildingSite>(cSel)));
×
609
            return true;
×
610
        }
611

612
        action_tabs.watch = true;
1✔
613
        // Unser Land
614
        if(worldViewer.IsOwner(cSel))
1✔
615
        {
616
            const BuildingQuality bq = worldViewer.GetBQ(cSel);
1✔
617
            // Kann hier was gebaut werden?
618
            if(bq >= BuildingQuality::Mine)
1✔
619
            {
620
                action_tabs.build = true;
1✔
621

622
                // Welches Gebäude kann gebaut werden?
623
                switch(bq)
1✔
624
                {
625
                    case BuildingQuality::Mine: action_tabs.build_tabs = iwAction::BuildTab::Mine; break;
×
626
                    case BuildingQuality::Hut: action_tabs.build_tabs = iwAction::BuildTab::Hut; break;
×
627
                    case BuildingQuality::House: action_tabs.build_tabs = iwAction::BuildTab::House; break;
×
628
                    case BuildingQuality::Castle: action_tabs.build_tabs = iwAction::BuildTab::Castle; break;
1✔
629
                    case BuildingQuality::Harbor: action_tabs.build_tabs = iwAction::BuildTab::Harbor; break;
×
630
                    default: break;
×
631
                }
632

633
                if(!worldViewer.GetWorld().IsFlagAround(cSel))
1✔
634
                    action_tabs.setflag = true;
1✔
635

636
                // Prüfen, ob sich Militärgebäude in der Nähe befinden, wenn nein, können auch eigene
637
                // Militärgebäude gebaut werden
638
                enable_military_buildings =
1✔
639
                  !worldViewer.GetWorld().IsMilitaryBuildingNearNode(cSel, worldViewer.GetPlayerId());
1✔
640
            } else if(bq == BuildingQuality::Flag)
×
641
                action_tabs.setflag = true;
×
642
            else if(selObj.GetType() == NodalObjectType::Flag)
×
643
                action_tabs.flag = true;
×
644

645
            if(selObj.GetType() != NodalObjectType::Flag && selObj.GetType() != NodalObjectType::Building)
1✔
646
            {
647
                // Check if there are roads
648
                for(const Direction dir : helpers::EnumRange<Direction>{})
16✔
649
                {
650
                    const PointRoad curRoad = worldViewer.GetVisiblePointRoad(cSel, dir);
6✔
651
                    if(curRoad != PointRoad::None)
6✔
652
                    {
653
                        action_tabs.cutroad = true;
×
654
                        action_tabs.upgradeRoad |= (curRoad == PointRoad::Normal);
×
655
                    }
656
                }
657
            }
658
        }
659
        // evtl ists ein feindliches Militärgebäude, welches NICHT im Nebel liegt?
660
        else if(worldViewer.GetVisibility(cSel) == Visibility::Visible)
×
661
        {
662
            if(selObj.GetType() == NodalObjectType::Building)
×
663
            {
664
                const auto* building = worldViewer.GetWorld().GetSpecObj<noBuilding>(cSel); //-V807
×
665
                BuildingType bt = building->GetBuildingType();
×
666

667
                // Only if trade is enabled
668
                if(worldViewer.GetWorld().GetGGS().isEnabled(AddonId::TRADE))
×
669
                {
670
                    // Allied warehouse? -> Show trade window
671
                    if(BuildingProperties::IsWareHouse(bt) && worldViewer.GetPlayer().IsAlly(building->GetPlayer()))
×
672
                    {
673
                        WINDOWMANAGER.Show(std::make_unique<iwTrade>(*static_cast<const nobBaseWarehouse*>(building),
×
674
                                                                     worldViewer, GAMECLIENT));
×
675
                        return true;
×
676
                    }
677
                }
678

679
                // Ist es ein gewöhnliches Militärgebäude?
680
                if(BuildingProperties::IsMilitary(bt))
×
681
                {
682
                    // Dann darf es nicht neu gebaut sein!
683
                    if(!static_cast<const nobMilitary*>(building)->IsNewBuilt())
×
684
                        action_tabs.attack = true;
×
685
                }
686
                // oder ein HQ oder Hafen?
687
                else if(bt == BuildingType::Headquarters || bt == BuildingType::HarborBuilding)
×
688
                    action_tabs.attack = true;
×
689
                action_tabs.sea_attack =
×
690
                  action_tabs.attack && worldViewer.GetWorld().GetGGS().isEnabled(AddonId::SEA_ATTACK);
×
691
            }
692
        }
693

694
        // Bisheriges Actionfenster schließen, falls es eins gab
695
        // aktuelle Mausposition merken, da diese durch das Schließen verändert werden kann
696
        if(actionwindow)
1✔
697
            actionwindow->Close();
×
698
        VIDEODRIVER.SetMousePos(mc.pos);
1✔
699

700
        ShowActionWindow(action_tabs, cSel, mc.pos, enable_military_buildings);
1✔
701
    }
702

703
    return true;
2✔
704
}
705

706
bool dskGameInterface::Msg_LeftDown(const MouseCoords& mc)
3✔
707
{
708
    const glArchivItem_Bitmap& imgButtonBar = *LOADER.GetImageN("resource", 29);
6✔
709
    const auto btOrig = DrawPoint(VIDEODRIVER.GetRenderSize().x / 2 - imgButtonBar.getWidth() / 2,
3✔
710
                                  VIDEODRIVER.GetRenderSize().y - imgButtonBar.getHeight())
3✔
711
                        + btOffset;
3✔
712
    if(IsPointInRect(mc.pos, Rect(btOrig, btSize * 4u)))
3✔
UNCOV
713
        return false;
×
714

715
    if(!VIDEODRIVER.IsTouch())
3✔
716
    {
717
        // Start scrolling also on Ctrl + left click
718
        if(VIDEODRIVER.GetModKeyState().ctrl)
3✔
719
        {
720
            Msg_RightDown(mc);
1✔
721
            return true;
1✔
722
        } else if(isScrolling)
2✔
723
            StopScrolling();
2✔
724

725
        return ContextClick(mc);
2✔
726

727
    } else if(mc.num_tfingers < 2)
×
728
        touchDuration = VIDEODRIVER.GetTickCount();
×
729
    else if(isScrolling) // 2 fingers down -> zoom mode. Do not click or scroll map
×
730
        StopScrolling();
×
731

732
    return true;
×
733
}
734

735
bool dskGameInterface::Msg_LeftUp(const MouseCoords& mc)
2✔
736
{
737
    if(isScrolling)
2✔
738
    {
739
        StopScrolling();
1✔
740
        return true;
1✔
741
    }
742

743
    // num_tfingers is reduced after this function to check if it's still a touch event
744
    // Was touch duration short enough to trigger conext click?
745
    if(mc.num_tfingers == 1 && (VIDEODRIVER.GetTickCount() - touchDuration) < TOUCH_MAX_CLICK_INTERVAL)
1✔
746
        return ContextClick(mc);
×
747

748
    return false;
1✔
749
}
750

751
bool dskGameInterface::Msg_MouseMove(const MouseCoords& mc)
22✔
752
{
753
    if(!isScrolling)
22✔
754
    {
755
        if(mc.num_tfingers == 1)
13✔
756
            Msg_RightDown(mc);
×
757
        else
758
            return false;
13✔
759
    }
760

761
    if(SETTINGS.interface.mapScrollMode == MapScrollMode::GrabAndDrag)
9✔
762
    {
763
        const Position mapPos = gwv.ViewPosToMap(mc.pos);
1✔
764
        gwv.MoveBy(-(mapPos - startScrollPt));
1✔
765
        startScrollPt = mapPos;
1✔
766
    } else
767
    {
768
        int acceleration = SETTINGS.global.smartCursor ? 2 : 3;
8✔
769

770
        if(SETTINGS.interface.mapScrollMode == MapScrollMode::ScrollSame)
8✔
771
            acceleration = -acceleration;
1✔
772

773
        gwv.MoveBy((mc.pos - startScrollPt) * acceleration);
8✔
774
        VIDEODRIVER.SetMousePos(startScrollPt);
8✔
775

776
        if(!SETTINGS.global.smartCursor)
8✔
777
            startScrollPt = mc.pos;
×
778
    }
779

780
    return true;
9✔
781
}
782

783
bool dskGameInterface::Msg_RightDown(const MouseCoords& mc)
7✔
784
{
785
    if(SETTINGS.interface.mapScrollMode == MapScrollMode::GrabAndDrag)
7✔
786
        StartScrolling(gwv.ViewPosToMap(mc.pos));
1✔
787
    else
788
        StartScrolling(mc.pos);
6✔
789
    return true;
7✔
790
}
791

792
bool dskGameInterface::Msg_RightUp(const MouseCoords& /*mc*/) //-V524
4✔
793
{
794
    if(isScrolling)
4✔
795
        StopScrolling();
4✔
796
    return false;
4✔
797
}
798

799
bool dskGameInterface::Msg_KeyDown(const KeyEvent& ke)
15✔
800
{
801
    cheatCommandTracker_.onKeyEvent(ke);
15✔
802

803
    switch(ke.kt)
15✔
804
    {
805
        default: break;
15✔
806
        case KeyType::Return: // Open chat
×
807
            WINDOWMANAGER.Show(std::make_unique<iwChat>(this));
×
808
            return true;
×
809

810
        case KeyType::Left: // Scroll left
×
811
            gwv.MoveBy({-30, 0});
×
812
            return true;
×
813
        case KeyType::Right: // Scroll right
×
814
            gwv.MoveBy({30, 0});
×
815
            return true;
×
816
        case KeyType::Up: // Scroll up
×
817
            gwv.MoveBy({0, -30});
×
818
            return true;
×
819
        case KeyType::Down: // Scroll down
×
820
            gwv.MoveBy({0, 30});
×
821
            return true;
×
822

823
        case KeyType::F2: // Open save game window
×
824
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwSave>());
×
825
            return true;
×
826
        case KeyType::F3: // Map debug window
×
827
        {
828
            const bool replayMode = GAMECLIENT.IsReplayModeOn();
×
829
            if(replayMode)
×
830
                DisableFoW(true);
×
831
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwMapDebug>(gwv, game_->world_.IsSinglePlayer() || replayMode));
×
832
            return true;
×
833
        }
834
        case KeyType::F8:
×
835
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwTextfile>("keyboardlayout.txt", _("Keyboard layout")));
×
836
            return true;
×
837
        case KeyType::F9:
×
838
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwTextfile>("readme.txt", _("Readme!")));
×
839
            return true;
×
NEW
840
        case KeyType::F10: WINDOWMANAGER.ToggleWindow(std::make_unique<iwSettings>()); return true;
×
841
        case KeyType::F11: // Music player (midi files)
×
842
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwMusicPlayer>());
×
843
            return true;
×
844
        case KeyType::F12: // Ingame options
×
845
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwOptionsWindow>(gwv.GetSoundMgr()));
×
846
            return true;
×
847
    }
848

849
    switch(ke.c)
15✔
850
    {
851
        case '+':
×
852
            if(GAMECLIENT.IsReplayModeOn() || game_->world_.IsSinglePlayer())
×
853
                GAMECLIENT.IncreaseSpeed();
×
854
            return true;
×
855
        case '-':
×
856
            if(GAMECLIENT.IsReplayModeOn() || game_->world_.IsSinglePlayer())
×
857
                GAMECLIENT.DecreaseSpeed();
×
858
            return true;
×
859

860
        // Switch to specific player
861
        case '1':
×
862
        case '2':
863
        case '3':
864
        case '4':
865
        case '5':
866
        case '6':
867
        case '7':
868
        case '8':
869
        {
870
            unsigned playerIdx = ke.c - '1';
×
871
            if(GAMECLIENT.IsReplayModeOn())
×
872
            {
873
                unsigned oldPlayerId = worldViewer.GetPlayerId();
×
874
                GAMECLIENT.ChangePlayerIngame(worldViewer.GetPlayerId(), playerIdx);
×
875
                RTTR_Assert(worldViewer.GetPlayerId() == oldPlayerId || worldViewer.GetPlayerId() == playerIdx);
×
876
            } else if(playerIdx < worldViewer.GetWorld().GetNumPlayers())
×
877
            {
878
                // On multiplayer this currently asyncs, but as this is a debug feature anyway just disable it.
879
                // If this should be enabled again, look into the handling/clearing of accumulated GCs
880
                if(game_->world_.IsSinglePlayer())
×
881
                {
882
                    const GamePlayer& player = worldViewer.GetWorld().GetPlayer(playerIdx);
×
883
                    if(player.ps == PlayerState::AI && player.aiInfo.type == AI::Type::Dummy)
×
884
                        GAMECLIENT.RequestSwapToPlayer(playerIdx);
×
885
                }
886
            }
887
            return true;
×
888
        }
889

890
        case 'b': gwv.MoveToLastPosition(); return true;
×
891
        case 'v':
×
892
            if(game_->world_.IsSinglePlayer())
×
893
                GAMECLIENT.IncreaseSpeed(true);
×
894
            return true;
×
895
        case 'c': // Show/hide building names
×
896
            gwv.ToggleShowNames();
×
897
            return true;
×
898
        case 'd': // Enable/Disable fog of war (in replay mode)
×
899
            ToggleFoW();
×
900
            return true;
×
901
        case 'h': // Go to HQ
×
902
        {
903
            const GamePlayer& player = worldViewer.GetPlayer();
×
904
            // HQ might not exist anymore
905
            if(player.GetHQPos().isValid())
×
906
                gwv.MoveToMapPt(player.GetHQPos());
×
907
        }
908
            return true;
×
909
        case 'i': // Show inventory
×
910
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwInventory>(worldViewer.GetPlayer()));
×
911
            return true;
×
912
        case 'j': // Skip GFs (fast forward)
×
913
            if(game_->world_.IsSinglePlayer() || GAMECLIENT.IsReplayModeOn())
×
914
                WINDOWMANAGER.ToggleWindow(std::make_unique<iwSkipGFs>(gwv));
×
915
            return true;
×
916
        case 'l': // Show minimap
×
917
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwMinimap>(minimap, gwv));
×
918
            return true;
×
919
        case 'm': // Show main menu
2✔
920
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwMainMenu>(gwv, GAMECLIENT));
2✔
921
            return true;
2✔
922
        case 'n': // Show message window
×
923
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwPostWindow>(gwv, GetPostBox()));
×
924
            UpdatePostIcon(GetPostBox().GetNumMsgs(), false);
×
925
            return true;
×
926
        case 'p': // Pause
×
927
            GAMECLIENT.TogglePause();
×
928
            return true;
×
929
        case 'q': // Quit game (dialog)
×
930
            if(ke.alt)
×
931
                WINDOWMANAGER.ToggleWindow(std::make_unique<iwEndgame>());
×
932
            return true;
×
933
        case 's': // Show/hide productivity overlay
×
934
            gwv.ToggleShowProductivity();
×
935
            return true;
×
936
        case ' ': // Show/hide construction aid
×
937
            // workaround for Wayland which does not capture SDLK_SPACE when SDL_StartTextInput() was called
938
            gwv.ToggleShowBQ();
×
939
            return true;
×
940
        case 26: // ctrl+z
×
941
            gwv.SetZoomFactor(ZOOM_FACTORS[ZOOM_DEFAULT_INDEX]);
×
942
            return true;
×
943
        case 'z':
9✔
944
            if(ke.ctrl) // Reset zoom
9✔
945
                gwv.SetZoomFactor(ZOOM_FACTORS[ZOOM_DEFAULT_INDEX]);
2✔
946
            else // zoom in
947
                gwv.SetZoomFactor(getNextZoomLevel(gwv.GetCurrentTargetZoomFactor()));
7✔
948
            return true;
9✔
949
        case 'Z': // shift-z, zoom out
4✔
950
            gwv.SetZoomFactor(getPreviousZoomLevel(gwv.GetCurrentTargetZoomFactor()));
4✔
951
            return true;
4✔
952
    }
953

954
    return false;
×
955
}
956

957
bool dskGameInterface::Msg_WheelUp(const MouseCoords&)
12✔
958
{
959
    WheelZoom(ZOOM_WHEEL_INCREMENT);
12✔
960
    return true;
12✔
961
}
962
bool dskGameInterface::Msg_WheelDown(const MouseCoords&)
13✔
963
{
964
    WheelZoom(-ZOOM_WHEEL_INCREMENT);
13✔
965
    return true;
13✔
966
}
967

968
void dskGameInterface::WheelZoom(const float step)
25✔
969
{
970
    auto targetZoomFactor = gwv.GetCurrentTargetZoomFactor() * (1 + step);
25✔
971
    targetZoomFactor = std::clamp(targetZoomFactor, ZOOM_FACTORS.front(), ZOOM_FACTORS.back());
25✔
972
    if(targetZoomFactor > 1 - ZOOM_WHEEL_INCREMENT && targetZoomFactor < 1 + ZOOM_WHEEL_INCREMENT)
25✔
973
        targetZoomFactor = 1.f; // Snap to 100%
2✔
974

975
    gwv.SetZoomFactor(targetZoomFactor);
25✔
976
}
25✔
977

978
void dskGameInterface::OnBuildingNote(const BuildingNote& note)
×
979
{
980
    switch(note.type)
×
981
    {
982
        case BuildingNote::Constructed:
×
983
        case BuildingNote::Destroyed:
984
        case BuildingNote::Lost:
985
            // Close the related window as the building does not exist anymore
986
            // In "Constructed" this means the buildingsite
987
            WINDOWMANAGER.Close(CGI_BUILDING + MapBase::CreateGUIID(note.pos));
×
988
            break;
×
989
        default: break;
×
990
    }
991
}
×
992

993
void dskGameInterface::Run()
×
994
{
995
    // Reset draw counter of the trees before drawing
996
    noTree::ResetDrawCounter();
×
997

998
    unsigned water_percent;
999
    // Draw mouse only if not on window
1000
    bool drawMouse = WINDOWMANAGER.FindWindowAtPos(VIDEODRIVER.GetMousePos()) == nullptr;
×
1001
    gwv.Draw(road, actionwindow != nullptr ? actionwindow->GetSelectedPt() : MapPoint::Invalid(), drawMouse,
×
1002
             &water_percent);
1003

1004
    // Indicate that the game is paused by darkening the screen (dark semi-transparent overlay)
1005
    if(GAMECLIENT.IsPaused())
×
1006
        DrawRectangle(Rect(DrawPoint(0, 0), VIDEODRIVER.GetRenderSize()), COLOR_SHADOW);
×
1007
    else
1008
    {
1009
        // Play ambient sounds if game is not paused
1010
        worldViewer.GetSoundMgr().playOceanBrawling(water_percent);
×
1011
        worldViewer.GetSoundMgr().playBirdSounds(noTree::QueryDrawCounter());
×
1012
    }
1013

1014
    messenger.Draw();
×
1015
}
×
1016

1017
void dskGameInterface::GI_StartRoadBuilding(const MapPoint startPt, bool waterRoad)
3✔
1018
{
1019
    // Im Replay keine Straßen bauen
1020
    if(GAMECLIENT.IsReplayModeOn())
3✔
1021
        return;
×
1022

1023
    road.mode = waterRoad ? RoadBuildMode::Boat : RoadBuildMode::Normal;
3✔
1024
    road.route.clear();
3✔
1025
    road.start = road.point = startPt;
3✔
1026
    WINDOWMANAGER.SetCursor(Cursor::Remove);
3✔
1027
}
1028

1029
void dskGameInterface::GI_CancelRoadBuilding()
2✔
1030
{
1031
    if(road.mode == RoadBuildMode::Disabled)
2✔
1032
        return;
×
1033
    road.mode = RoadBuildMode::Disabled;
2✔
1034
    worldViewer.RemoveVisualRoad(road.start, road.route);
2✔
1035
    WINDOWMANAGER.SetCursor(isScrolling ? Cursor::Scroll : Cursor::Hand);
2✔
1036
}
1037

1038
bool dskGameInterface::BuildRoadPart(MapPoint& cSel)
3✔
1039
{
1040
    std::vector<Direction> new_route =
1041
      FindPathForRoad(worldViewer, road.point, cSel, road.mode == RoadBuildMode::Boat, 100);
6✔
1042
    // Weg gefunden?
1043
    if(new_route.empty())
3✔
1044
        return false;
×
1045

1046
    // Test on water way length
1047
    if(road.mode == RoadBuildMode::Boat)
3✔
1048
    {
1049
        unsigned char index = worldViewer.GetWorld().GetGGS().getSelection(AddonId::MAX_WATERWAY_LENGTH);
×
1050

1051
        RTTR_Assert(index < waterwayLengths.size());
×
1052
        const unsigned max_length = waterwayLengths[index];
×
1053

1054
        unsigned length = road.route.size() + new_route.size();
×
1055

1056
        // max_length == 0 heißt beliebig lang, ansonsten
1057
        // Weg zurechtstutzen.
1058
        if(max_length > 0)
×
1059
        {
1060
            while(length > max_length)
×
1061
            {
1062
                new_route.pop_back();
×
1063
                --length;
×
1064
            }
1065
        }
1066
    }
1067

1068
    // Weg (visuell) bauen
1069
    for(const auto dir : new_route)
22✔
1070
    {
1071
        worldViewer.SetVisiblePointRoad(road.point, dir,
19✔
1072
                                        (road.mode == RoadBuildMode::Boat) ? PointRoad::Boat : PointRoad::Normal);
19✔
1073
        worldViewer.RecalcBQForRoad(road.point);
19✔
1074
        road.point = worldViewer.GetWorld().GetNeighbour(road.point, dir);
19✔
1075
    }
1076
    worldViewer.RecalcBQForRoad(road.point);
3✔
1077

1078
    // Zielpunkt updaten (für Wasserweg)
1079
    cSel = road.point;
3✔
1080

1081
    road.route.insert(road.route.end(), new_route.begin(), new_route.end());
3✔
1082

1083
    return true;
3✔
1084
}
1085

1086
unsigned dskGameInterface::GetIdInCurBuildRoad(const MapPoint pt)
1✔
1087
{
1088
    MapPoint curPt = road.start;
1✔
1089
    for(unsigned i = 0; i < road.route.size(); ++i)
2✔
1090
    {
1091
        if(curPt == pt)
2✔
1092
            return i + 1;
1✔
1093

1094
        curPt = worldViewer.GetNeighbour(curPt, road.route[i]);
1✔
1095
    }
1096
    return 0;
×
1097
}
1098

1099
void dskGameInterface::ShowRoadWindow(const Position& mousePos)
×
1100
{
1101
    roadwindow = &WINDOWMANAGER.Show(
×
1102
      std::make_unique<iwRoadWindow>(*this, worldViewer.GetBQ(road.point) != BuildingQuality::Nothing, mousePos), true);
×
1103
}
×
1104

1105
void dskGameInterface::ShowActionWindow(const iwAction::Tabs& action_tabs, MapPoint cSel, const DrawPoint& mousePos,
2✔
1106
                                        const bool enable_military_buildings)
1107
{
1108
    const GameWorldBase& world = worldViewer.GetWorld();
2✔
1109

1110
    iwAction::Params params;
2✔
1111

1112
    // Sind wir am Wasser?
1113
    if(action_tabs.setflag)
2✔
1114
    {
1115
        auto isWater = [](const auto& desc) { return desc.kind == TerrainKind::Water; };
6✔
1116
        if(world.HasTerrain(cSel, isWater))
1✔
1117
            params = iwAction::FlagType::WaterFlag;
×
1118
    }
1119

1120
    // Wenn es einen Flaggen-Tab gibt, dann den Flaggentyp herausfinden und die Art des Fensters entsprechende setzen
1121
    if(action_tabs.flag)
2✔
1122
    {
1123
        if(world.GetNO(world.GetNeighbour(cSel, Direction::NorthWest))->GetGOT() == GO_Type::NobHq)
×
1124
            params = iwAction::FlagType::HQ;
×
1125
        else if(world.GetNO(cSel)->GetType() == NodalObjectType::Flag)
×
1126
        {
1127
            if(world.GetSpecObj<noFlag>(cSel)->GetFlagType() == FlagType::Water)
×
1128
                params = iwAction::FlagType::WaterFlag;
×
1129
        }
1130
    }
1131

1132
    // Angriffstab muss wissen, wieviel Soldaten maximal entsendet werden können
1133
    if(action_tabs.attack)
2✔
1134
    {
1135
        params = worldViewer.GetNumSoldiersForAttack(cSel);
×
1136
    }
1137

1138
    actionwindow = &WINDOWMANAGER.Show(
2✔
1139
      std::make_unique<iwAction>(*this, gwv, action_tabs, cSel, mousePos, params, enable_military_buildings), true);
2✔
1140
}
2✔
1141

1142
void dskGameInterface::OnChatCommand(const std::string& cmd)
×
1143
{
1144
    cheatCommandTracker_.onChatCommand(cmd);
×
1145

1146
    if(cmd == "surrender")
×
1147
        GAMECLIENT.Surrender();
×
1148
    else if(cmd == "async")
×
1149
        (void)RANDOM.Rand(RANDOM_CONTEXT2(0), 255);
×
1150
    else if(cmd == "segfault")
×
1151
    {
1152
        char* x = nullptr;
×
1153
        *x = 1; //-V522 // NOLINT
×
1154
    } else if(cmd == "reload")
×
1155
    {
1156
        WorldDescription newDesc;
×
1157
        GameDataLoader gdLoader(newDesc);
×
1158
        if(gdLoader.Load())
×
1159
        {
1160
            const_cast<GameWorld&>(game_->world_).GetDescriptionWriteable() = newDesc;
×
1161
            worldViewer.InitTerrainRenderer();
×
1162
        }
1163
    }
1164
}
×
1165

1166
void dskGameInterface::GI_BuildRoad()
×
1167
{
1168
    if(GAMECLIENT.BuildRoad(road.start, road.mode == RoadBuildMode::Boat, road.route))
×
1169
    {
1170
        road.mode = RoadBuildMode::Disabled;
×
1171
        WINDOWMANAGER.SetCursor(Cursor::Hand);
×
1172
    }
1173
}
×
1174

1175
void dskGameInterface::Msg_WindowClosed(IngameWindow& wnd)
2✔
1176
{
1177
    if(actionwindow == &wnd)
2✔
1178
        actionwindow = nullptr;
1✔
1179
    else if(roadwindow == &wnd)
1✔
1180
        roadwindow = nullptr;
×
1181
}
2✔
1182

1183
void dskGameInterface::GI_FlagDestroyed(const MapPoint pt)
×
1184
{
1185
    // Im Wegbaumodus und haben wir von hier eine Flagge gebaut?
1186
    if(road.mode != RoadBuildMode::Disabled && road.start == pt)
×
1187
    {
1188
        GI_CancelRoadBuilding();
×
1189
    }
1190

1191
    // Evtl Actionfenster schließen, da sich das ja auch auf diese Flagge bezieht
1192
    if(actionwindow)
×
1193
    {
1194
        if(actionwindow->GetSelectedPt() == pt)
×
1195
            actionwindow->Close();
×
1196
    }
1197
}
×
1198

1199
void dskGameInterface::CI_PlayerLeft(const unsigned playerId)
×
1200
{
1201
    // Info-Meldung ausgeben
1202
    std::string text =
1203
      helpers::format(_("Player '%s' left the game!"), worldViewer.GetWorld().GetPlayer(playerId).name);
×
1204
    messenger.AddMessage("", 0, ChatDestination::System, text, COLOR_RED);
×
1205
    // Im Spiel anzeigen, dass die KI das Spiel betreten hat
1206
    text = helpers::format(_("Player '%s' joined the game!"), "KI");
×
1207
    messenger.AddMessage("", 0, ChatDestination::System, text, COLOR_GREEN);
×
1208
}
×
1209

1210
void dskGameInterface::CI_GGSChanged(const GlobalGameSettings& /*ggs*/)
×
1211
{
1212
    // TODO: print what has changed
1213
    const std::string text = helpers::format(_("Note: Game settings changed by the server%s"), "");
×
1214
    messenger.AddMessage("", 0, ChatDestination::System, text);
×
1215
}
×
1216

1217
void dskGameInterface::CI_Chat(const unsigned playerId, const ChatDestination cd, const std::string& msg)
×
1218
{
1219
    messenger.AddMessage(worldViewer.GetWorld().GetPlayer(playerId).name,
×
1220
                         worldViewer.GetWorld().GetPlayer(playerId).color, cd, msg);
×
1221
}
×
1222

1223
void dskGameInterface::CI_Async(const std::string& checksums_list)
×
1224
{
1225
    messenger.AddMessage("", 0, ChatDestination::System,
×
1226
                         _("The Game is not in sync. Checksums of some players don't match."), COLOR_RED);
1227
    messenger.AddMessage("", 0, ChatDestination::System, checksums_list, COLOR_YELLOW);
×
1228
    messenger.AddMessage("", 0, ChatDestination::System, _("A auto-savegame is created..."), COLOR_RED);
×
1229
}
×
1230

1231
void dskGameInterface::CI_ReplayAsync(const std::string& msg)
×
1232
{
1233
    messenger.AddMessage("", 0, ChatDestination::System, msg, COLOR_RED);
×
1234
}
×
1235

1236
void dskGameInterface::CI_ReplayEndReached(const std::string& msg)
×
1237
{
1238
    messenger.AddMessage("", 0, ChatDestination::System, msg, COLOR_BLUE);
×
1239
}
×
1240

1241
void dskGameInterface::CI_GamePaused()
×
1242
{
1243
    messenger.AddMessage(_("SYSTEM"), COLOR_GREY, ChatDestination::System, _("Game was paused."));
×
1244
}
×
1245

1246
void dskGameInterface::CI_GameResumed()
×
1247
{
1248
    messenger.AddMessage(_("SYSTEM"), COLOR_GREY, ChatDestination::System, _("Game was resumed."));
×
1249
}
×
1250

1251
void dskGameInterface::CI_Error(const ClientError ce)
×
1252
{
1253
    messenger.AddMessage("", 0, ChatDestination::System, ClientErrorToStr(ce), COLOR_RED);
×
1254
    GAMECLIENT.SetPause(true);
×
1255
}
×
1256

1257
/**
1258
 *  Status: Verbindung verloren.
1259
 */
1260
void dskGameInterface::LC_Status_ConnectionLost()
×
1261
{
1262
    messenger.AddMessage("", 0, ChatDestination::System, _("Lost connection to lobby!"), COLOR_RED);
×
1263
}
×
1264

1265
/**
1266
 *  (Lobby-)Status: Benutzerdefinierter Fehler
1267
 */
1268
void dskGameInterface::LC_Status_Error(const std::string& error)
×
1269
{
1270
    messenger.AddMessage("", 0, ChatDestination::System, error, COLOR_RED);
×
1271
}
×
1272

1273
void dskGameInterface::CI_PlayersSwapped(const unsigned player1, const unsigned player2)
×
1274
{
1275
    // Meldung anzeigen
1276
    std::string text = "Player '" + worldViewer.GetWorld().GetPlayer(player1).name + "' switched to player '"
×
1277
                       + worldViewer.GetWorld().GetPlayer(player2).name + "'";
×
1278
    messenger.AddMessage("", 0, ChatDestination::System, text, COLOR_YELLOW);
×
1279

1280
    // Sichtbarkeiten und Minimap neu berechnen, wenn wir ein von den beiden Spielern sind
1281
    const unsigned localPlayerId = worldViewer.GetPlayerId();
×
1282
    if(player1 == localPlayerId || player2 == localPlayerId)
×
1283
    {
1284
        worldViewer.ChangePlayer(player1 == localPlayerId ? player2 : player1);
×
1285
        // Set visual settings back to the actual ones
1286
        GAMECLIENT.ResetVisualSettings();
×
1287
        minimap.UpdateAll();
×
1288
        InitPlayer();
×
1289
    }
1290
}
×
1291

1292
/**
1293
 *  Wenn ein Spieler verloren hat
1294
 */
1295
void dskGameInterface::GI_PlayerDefeated(const unsigned playerId)
×
1296
{
1297
    const std::string text =
1298
      helpers::format(_("Player '%s' was defeated!"), worldViewer.GetWorld().GetPlayer(playerId).name);
×
1299
    messenger.AddMessage("", 0, ChatDestination::System, text, COLOR_ORANGE);
×
1300

1301
    /// Lokaler Spieler?
1302
    if(playerId == worldViewer.GetPlayerId())
×
1303
    {
1304
        /// Sichtbarkeiten neu berechnen
1305
        worldViewer.RecalcAllColors();
×
1306
        // Minimap updaten
1307
        minimap.UpdateAll();
×
1308
    }
1309
}
×
1310

1311
void dskGameInterface::GI_UpdateMinimap(const MapPoint pt)
×
1312
{
1313
    // Minimap Bescheid sagen
1314
    minimap.UpdateNode(pt);
×
1315
}
×
1316

1317
void dskGameInterface::GI_UpdateMapVisibility()
×
1318
{
1319
    // recalculate visibility
1320
    worldViewer.RecalcAllColors();
×
1321
    // update minimap
1322
    minimap.UpdateAll();
×
1323
}
×
1324

1325
/**
1326
 *  Bündnisvertrag wurde abgeschlossen oder abgebrochen --> Minimap updaten
1327
 */
1328
void dskGameInterface::GI_TreatyOfAllianceChanged(unsigned playerId)
×
1329
{
1330
    // Nur wenn Team-Sicht aktiviert ist, können sihc die Sichtbarkeiten auch ändern
1331
    if(playerId == worldViewer.GetPlayerId() && worldViewer.GetWorld().GetGGS().teamView)
×
1332
    {
1333
        /// Sichtbarkeiten neu berechnen
1334
        worldViewer.RecalcAllColors();
×
1335
        // Minimap updaten
1336
        minimap.UpdateAll();
×
1337
    }
1338
}
×
1339

1340
/**
1341
 *  Baut Weg zurück von Ende bis zu start_id
1342
 */
1343
void dskGameInterface::DemolishRoad(const unsigned start_id)
1✔
1344
{
1345
    RTTR_Assert(start_id > 0);
1✔
1346
    for(unsigned i = road.route.size(); i >= start_id; --i)
6✔
1347
    {
1348
        MapPoint t = road.point;
5✔
1349
        road.point = worldViewer.GetWorld().GetNeighbour(road.point, road.route[i - 1] + 3u);
5✔
1350
        worldViewer.SetVisiblePointRoad(road.point, road.route[i - 1], PointRoad::None);
5✔
1351
        worldViewer.RecalcBQForRoad(t);
5✔
1352
    }
1353

1354
    road.route.resize(start_id - 1);
1✔
1355
}
1✔
1356

1357
/**
1358
 *  Updatet das Post-Icon mit der Nachrichtenanzahl und der Taube
1359
 */
1360
void dskGameInterface::UpdatePostIcon(const unsigned postmessages_count, bool showPigeon)
6✔
1361
{
1362
    // Taube setzen oder nicht (Post)
1363
    if(postmessages_count == 0 || !showPigeon)
6✔
1364
        GetCtrl<ctrlImageButton>(3)->SetImage(LOADER.GetImageN("io", 62));
12✔
1365
    else
1366
        GetCtrl<ctrlImageButton>(3)->SetImage(LOADER.GetImageN("io", 59));
×
1367

1368
    // und Anzahl der Postnachrichten aktualisieren
1369
    if(postmessages_count > 0)
6✔
1370
    {
1371
        GetCtrl<ctrlText>(ID_txtNumMsg)->SetText(std::to_string(postmessages_count));
×
1372
    } else
1373
        GetCtrl<ctrlText>(ID_txtNumMsg)->SetText("");
6✔
1374
}
6✔
1375

1376
/**
1377
 *  Neue Post-Nachricht eingetroffen
1378
 */
1379
void dskGameInterface::NewPostMessage(const PostMsg& msg, const unsigned msgCt)
×
1380
{
1381
    UpdatePostIcon(msgCt, true);
×
1382
    SoundEffect soundEffect = msg.GetSoundEffect();
×
1383
    switch(soundEffect)
×
1384
    {
1385
        case SoundEffect::Pidgeon: LOADER.GetSoundN("sound", 114)->Play(100, false); break;
×
1386
        case SoundEffect::Fanfare: LOADER.GetSoundN("sound", 110)->Play(100, false);
×
1387
    }
1388
}
×
1389

1390
/**
1391
 *  Es wurde eine Postnachricht vom Spieler gelöscht
1392
 */
1393
void dskGameInterface::PostMessageDeleted(const unsigned msgCt)
×
1394
{
1395
    UpdatePostIcon(msgCt, false);
×
1396
}
×
1397

1398
/**
1399
 *  Ein Spieler hat das Spiel gewonnen.
1400
 */
1401
void dskGameInterface::GI_Winner(const unsigned playerId)
×
1402
{
1403
    const std::string name = worldViewer.GetWorld().GetPlayer(playerId).name;
×
1404
    const std::string text = (boost::format(_("Player '%s' is the winner!")) % name).str();
×
1405
    messenger.AddMessage("", 0, ChatDestination::System, text, COLOR_ORANGE);
×
1406
    WINDOWMANAGER.Show(std::make_unique<iwVictory>(std::vector<std::string>(1, name)));
×
1407
}
×
1408

1409
/**
1410
 *  Ein Team hat das Spiel gewonnen.
1411
 */
1412
void dskGameInterface::GI_TeamWinner(const unsigned playerMask)
×
1413
{
1414
    std::vector<std::string> winners;
×
1415
    const GameWorldBase& world = worldViewer.GetWorld();
×
1416
    for(unsigned i = 0; i < world.GetNumPlayers(); i++)
×
1417
    {
1418
        if(playerMask & (1 << i))
×
1419
            winners.push_back(world.GetPlayer(i).name);
×
1420
    }
1421
    const std::string text =
1422
      (boost::format(_("%1% are the winners!")) % helpers::join(winners, ", ", _(" and "))).str();
×
1423
    messenger.AddMessage("", 0, ChatDestination::System, text, COLOR_ORANGE);
×
1424
    WINDOWMANAGER.Show(std::make_unique<iwVictory>(winners));
×
1425
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc