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

Return-To-The-Roots / s25client / 19394159164

15 Nov 2025 06:57PM UTC coverage: 50.558% (+0.09%) from 50.469%
19394159164

Pull #1823

github

web-flow
Merge 5727ab542 into d3618af1f
Pull Request #1823: Make sure zooming via mouse goes back to 1.0

38 of 57 new or added lines in 6 files covered. (66.67%)

2 existing lines in 1 file now uncovered.

22547 of 44596 relevant lines covered (50.56%)

36145.67 hits per line

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

35.05
/libs/s25main/desktops/dskGameInterface.cpp
1
// Copyright (C) 2005 - 2025 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/iwShip.h"
57
#include "ingameWindows/iwSkipGFs.h"
58
#include "ingameWindows/iwStatistics.h"
59
#include "ingameWindows/iwTempleBuilding.h"
60
#include "ingameWindows/iwTextfile.h"
61
#include "ingameWindows/iwTools.h"
62
#include "ingameWindows/iwTrade.h"
63
#include "ingameWindows/iwTransport.h"
64
#include "ingameWindows/iwVictory.h"
65
#include "lua/GameDataLoader.h"
66
#include "network/GameClient.h"
67
#include "notifications/BuildingNote.h"
68
#include "notifications/NotificationManager.h"
69
#include "ogl/FontStyle.h"
70
#include "ogl/SoundEffectItem.h"
71
#include "ogl/glArchivItem_Bitmap_Player.h"
72
#include "ogl/glFont.h"
73
#include "pathfinding/FindPathForRoad.h"
74
#include "postSystem/PostBox.h"
75
#include "postSystem/PostMsg.h"
76
#include "random/Random.h"
77
#include "world/GameWorldBase.h"
78
#include "world/GameWorldViewer.h"
79
#include "nodeObjs/noFlag.h"
80
#include "nodeObjs/noTree.h"
81
#include "gameData/BuildingProperties.h"
82
#include "gameData/GameConsts.h"
83
#include "gameData/GuiConsts.h"
84
#include "gameData/TerrainDesc.h"
85
#include "gameData/const_gui_ids.h"
86
#include "liblobby/LobbyClient.h"
87
#include <algorithm>
88
#include <cstdio>
89
#include <utility>
90

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

102
dskGameInterface::dskGameInterface(std::shared_ptr<Game> game, std::shared_ptr<const NWFInfo> nwfInfo,
6✔
103
                                   unsigned playerIdx, bool initOGL)
6✔
104
    : Desktop(nullptr), game_(std::move(game)), nwfInfo_(std::move(nwfInfo)),
12✔
105
      worldViewer(playerIdx, const_cast<Game&>(*game_).world_),
6✔
106
      gwv(worldViewer, Position(0, 0), VIDEODRIVER.GetRenderSize()), cbb(*LOADER.GetPaletteN("pal5")),
18✔
107
      actionwindow(nullptr), roadwindow(nullptr), minimap(worldViewer), isScrolling(false),
6✔
108
      zoomLvl_(ZOOM_DEFAULT_INDEX), cheats_(const_cast<Game&>(*game_).world_), cheatCommandTracker_(cheats_)
18✔
109
{
110
    road.mode = RoadBuildMode::Disabled;
6✔
111
    road.point = MapPoint(0, 0);
6✔
112
    road.start = MapPoint(0, 0);
6✔
113

114
    SetScale(false);
6✔
115

116
    DrawPoint barPos((GetSize().x - LOADER.GetImageN("resource", 29)->getWidth()) / 2 + 44,
12✔
117
                     GetSize().y - LOADER.GetImageN("resource", 29)->getHeight() + 4);
18✔
118

119
    Extent btSize = Extent(37, 32);
6✔
120
    AddImageButton(ID_btMap, barPos, btSize, TextureColor::Green1, LOADER.GetImageN("io", 50), _("Map"))
6✔
121
      ->SetBorder(false);
12✔
122
    barPos.x += btSize.x;
6✔
123
    AddImageButton(ID_btOptions, barPos, btSize, TextureColor::Green1, LOADER.GetImageN("io", 192), _("Main selection"))
6✔
124
      ->SetBorder(false);
12✔
125
    barPos.x += btSize.x;
6✔
126
    AddImageButton(ID_btConstructionAid, barPos, btSize, TextureColor::Green1, LOADER.GetImageN("io", 83),
6✔
127
                   _("Construction aid mode"))
128
      ->SetBorder(false);
12✔
129
    barPos.x += btSize.x;
6✔
130
    AddImageButton(ID_btPost, barPos, btSize, TextureColor::Green1, LOADER.GetImageN("io", 62), _("Post office"))
6✔
131
      ->SetBorder(false);
12✔
132
    barPos += DrawPoint(18, 24);
6✔
133

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

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

138
    std::fill(borders.begin(), borders.end(), (glArchivItem_Bitmap*)(nullptr));
6✔
139
    cbb.loadEdges(LOADER.GetArchive("resource"));
12✔
140
    cbb.buildBorder(VIDEODRIVER.GetRenderSize(), borders);
6✔
141

142
    InitPlayer();
6✔
143
    if(initOGL)
6✔
144
        worldViewer.InitTerrainRenderer();
×
145

146
    VIDEODRIVER.setTargetFramerate(SETTINGS.video.framerate); // Use requested setting for ingame
6✔
147
}
6✔
148

149
void dskGameInterface::InitPlayer()
6✔
150
{
151
    // Jump to players HQ if it exists
152
    if(worldViewer.GetPlayer().GetHQPos().isValid())
6✔
153
        gwv.MoveToMapPt(worldViewer.GetPlayer().GetHQPos());
6✔
154

155
    evBld = worldViewer.GetWorld().GetNotifications().subscribe<BuildingNote>([this](const auto& note) {
12✔
156
        if(note.player == worldViewer.GetPlayerId())
×
157
            this->OnBuildingNote(note);
×
158
    });
6✔
159
    PostBox& postBox = GetPostBox();
6✔
160
    postBox.ObserveNewMsg([this](const auto& msg, auto msgCt) { this->NewPostMessage(msg, msgCt); });
6✔
161
    postBox.ObserveDeletedMsg([this](auto msgCt) { this->PostMessageDeleted(msgCt); });
6✔
162
    UpdatePostIcon(postBox.GetNumMsgs(), true);
6✔
163
}
6✔
164

165
PostBox& dskGameInterface::GetPostBox()
6✔
166
{
167
    PostBox* postBox = worldViewer.GetWorld().GetPostMgr().GetPostBox(worldViewer.GetPlayerId());
6✔
168
    if(!postBox)
6✔
169
        postBox = &worldViewer.GetWorldNonConst().GetPostMgr().AddPostBox(worldViewer.GetPlayerId());
6✔
170
    RTTR_Assert(postBox != nullptr);
6✔
171
    return *postBox;
6✔
172
}
173

174
dskGameInterface::~dskGameInterface()
6✔
175
{
176
    for(auto& border : borders)
30✔
177
        deletePtr(border);
24✔
178
    GAMECLIENT.RemoveInterface(this);
6✔
179
    LOBBYCLIENT.RemoveListener(this);
6✔
180
}
6✔
181

182
void dskGameInterface::SetActive(bool activate)
18✔
183
{
184
    if(activate == IsActive())
18✔
185
        return;
9✔
186
    if(!activate && isScrolling)
9✔
187
    {
188
        // Stay active if scrolling and no modal window is open
189
        const IngameWindow* wnd = WINDOWMANAGER.GetTopMostWindow();
1✔
190
        if(wnd && wnd->IsModal())
1✔
191
            StopScrolling();
×
192
        else
193
            return;
1✔
194
    }
195
    Desktop::SetActive(activate);
8✔
196
    // Do this here to allow previous screen to keep control
197
    if(activate)
8✔
198
    {
199
        GAMECLIENT.SetInterface(this);
6✔
200
        LOBBYCLIENT.AddListener(this);
6✔
201
        if(!game_->IsStarted())
6✔
202
        {
203
            GAMECLIENT.OnGameStart();
6✔
204

205
            ShowPersistentWindowsAfterSwitch();
6✔
206
        }
207
    }
208
}
209

210
void dskGameInterface::StopScrolling()
6✔
211
{
212
    isScrolling = false;
6✔
213
    WINDOWMANAGER.SetCursor(road.mode == RoadBuildMode::Disabled ? Cursor::Hand : Cursor::Remove);
6✔
214
}
6✔
215

216
void dskGameInterface::StartScrolling(const Position& mousePos)
11✔
217
{
218
    startScrollPt = mousePos;
11✔
219
    isScrolling = true;
11✔
220
    WINDOWMANAGER.SetCursor(Cursor::Scroll);
11✔
221
}
11✔
222

223
void dskGameInterface::ToggleFoW()
×
224
{
225
    DisableFoW(!GAMECLIENT.IsReplayFOWDisabled());
×
226
}
×
227

228
void dskGameInterface::DisableFoW(const bool hideFOW)
×
229
{
230
    GAMECLIENT.SetReplayFOW(hideFOW);
×
231
    // Notify viewer and minimap to recalculate the visibility
232
    worldViewer.RecalcAllColors();
×
233
    minimap.UpdateAll();
×
234
}
×
235

236
void dskGameInterface::ShowPersistentWindowsAfterSwitch()
6✔
237
{
238
    auto& windows = SETTINGS.windows.persistentSettings;
6✔
239

240
    if(windows[CGI_CHAT].isOpen)
6✔
241
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwChat>(this));
×
242
    if(windows[CGI_POSTOFFICE].isOpen)
6✔
243
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwPostWindow>(gwv, GetPostBox()));
×
244
    if(windows[CGI_DISTRIBUTION].isOpen)
6✔
245
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwDistribution>(gwv.GetViewer(), GAMECLIENT));
×
246
    if(windows[CGI_BUILDORDER].isOpen && gwv.GetWorld().GetGGS().isEnabled(AddonId::CUSTOM_BUILD_SEQUENCE))
6✔
247
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwBuildOrder>(gwv.GetViewer()));
×
248
    if(windows[CGI_TRANSPORT].isOpen)
6✔
249
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwTransport>(gwv.GetViewer(), GAMECLIENT));
×
250
    if(windows[CGI_MILITARY].isOpen)
6✔
251
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwMilitary>(gwv.GetViewer(), GAMECLIENT));
×
252
    if(windows[CGI_TOOLS].isOpen)
6✔
253
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwTools>(gwv.GetViewer(), GAMECLIENT));
×
254
    if(windows[CGI_INVENTORY].isOpen)
6✔
255
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwInventory>(gwv.GetViewer().GetPlayer()));
×
256
    if(windows[CGI_MINIMAP].isOpen)
6✔
257
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwMinimap>(minimap, gwv));
×
258
    if(windows[CGI_BUILDINGS].isOpen)
6✔
259
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwBuildings>(gwv, GAMECLIENT));
×
260
    if(windows[CGI_BUILDINGSPRODUCTIVITY].isOpen)
6✔
261
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwBuildingProductivities>(gwv.GetViewer().GetPlayer()));
×
262
    if(windows[CGI_MUSICPLAYER].isOpen)
6✔
263
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwMusicPlayer>());
×
264
    if(windows[CGI_STATISTICS].isOpen)
6✔
265
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwStatistics>(gwv.GetViewer()));
×
266
    if(windows[CGI_ECONOMICPROGRESS].isOpen && gwv.GetWorld().getEconHandler())
6✔
267
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwEconomicProgress>(gwv.GetViewer()));
×
268
    if(windows[CGI_DIPLOMACY].isOpen)
6✔
269
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwDiplomacy>(gwv.GetViewer(), GAMECLIENT));
×
270
    if(windows[CGI_SHIP].isOpen)
6✔
271
        WINDOWMANAGER.ShowAfterSwitch(
×
272
          std::make_unique<iwShip>(gwv, GAMECLIENT, gwv.GetViewer().GetPlayer().GetShipByID(0)));
×
273
    if(windows[CGI_MERCHANDISE_STATISTICS].isOpen)
6✔
274
        WINDOWMANAGER.ShowAfterSwitch(std::make_unique<iwMerchandiseStatistics>(gwv.GetViewer().GetPlayer()));
×
275
}
6✔
276

277
void dskGameInterface::Resize(const Extent& newSize)
×
278
{
279
    Window::Resize(newSize);
×
280

281
    // recreate borders
282
    for(auto& border : borders)
×
283
        deletePtr(border);
×
284
    cbb.buildBorder(newSize, borders);
×
285

286
    // move buttons
287
    DrawPoint barPos((newSize.x - LOADER.GetImageN("resource", 29)->getWidth()) / 2 + 44,
×
288
                     newSize.y - LOADER.GetImageN("resource", 29)->getHeight() + 4);
×
289

290
    auto* button = GetCtrl<ctrlButton>(ID_btMap);
×
291
    button->SetPos(barPos);
×
292

293
    barPos.x += button->GetSize().x;
×
294
    button = GetCtrl<ctrlButton>(ID_btOptions);
×
295
    button->SetPos(barPos);
×
296

297
    barPos.x += button->GetSize().x;
×
298
    button = GetCtrl<ctrlButton>(ID_btConstructionAid);
×
299
    button->SetPos(barPos);
×
300

301
    barPos.x += button->GetSize().x;
×
302
    button = GetCtrl<ctrlButton>(ID_btPost);
×
303
    button->SetPos(barPos);
×
304

305
    barPos += DrawPoint(18, 24);
×
306
    auto* text = GetCtrl<ctrlText>(ID_txtNumMsg);
×
307
    text->SetPos(barPos);
×
308

309
    gwv.Resize(newSize);
×
310
}
×
311

312
void dskGameInterface::Msg_ButtonClick(const unsigned ctrl_id)
×
313
{
314
    switch(ctrl_id)
×
315
    {
316
        case ID_btMap: WINDOWMANAGER.ToggleWindow(std::make_unique<iwMinimap>(minimap, gwv)); break;
×
317
        case ID_btOptions: WINDOWMANAGER.ToggleWindow(std::make_unique<iwMainMenu>(gwv, GAMECLIENT)); break;
×
318
        case ID_btConstructionAid:
×
319
            if(WINDOWMANAGER.IsDesktopActive())
×
320
                gwv.ToggleShowBQ();
×
321
            break;
×
322
        case ID_btPost:
×
323
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwPostWindow>(gwv, GetPostBox()));
×
324
            UpdatePostIcon(GetPostBox().GetNumMsgs(), false);
×
325
            break;
×
326
    }
327
}
×
328

329
void dskGameInterface::Msg_PaintBefore()
×
330
{
331
    Desktop::Msg_PaintBefore();
×
332

333
    // Spiel ausführen
334
    Run();
×
335

336
    /// Padding of the figures
337
    const DrawPoint figPadding(12, 12);
×
338
    const DrawPoint screenSize(VIDEODRIVER.GetRenderSize());
×
339
    // Rahmen zeichnen
340
    borders[0]->DrawFull(DrawPoint(0, 0));                                      // oben (mit Ecken)
×
341
    borders[1]->DrawFull(DrawPoint(0, screenSize.y - figPadding.y));            // unten (mit Ecken)
×
342
    borders[2]->DrawFull(DrawPoint(0, figPadding.y));                           // links
×
343
    borders[3]->DrawFull(DrawPoint(screenSize.x - figPadding.x, figPadding.y)); // rechts
×
344

345
    // The figure/statues and the button bar
346
    glArchivItem_Bitmap& imgFigLeftTop = *LOADER.GetImageN("resource", 17);
×
347
    glArchivItem_Bitmap& imgFigRightTop = *LOADER.GetImageN("resource", 18);
×
348
    glArchivItem_Bitmap& imgFigLeftBot = *LOADER.GetImageN("resource", 19);
×
349
    glArchivItem_Bitmap& imgFigRightBot = *LOADER.GetImageN("resource", 20);
×
350
    imgFigLeftTop.DrawFull(figPadding);
×
351
    imgFigRightTop.DrawFull(DrawPoint(screenSize.x - figPadding.x - imgFigRightTop.getWidth(), figPadding.y));
×
352
    imgFigLeftBot.DrawFull(DrawPoint(figPadding.x, screenSize.y - figPadding.y - imgFigLeftBot.getHeight()));
×
353
    imgFigRightBot.DrawFull(screenSize - figPadding - imgFigRightBot.GetSize());
×
354

355
    glArchivItem_Bitmap& imgButtonBar = *LOADER.GetImageN("resource", 29);
×
356
    imgButtonBar.DrawFull(
×
357
      DrawPoint((screenSize.x - imgButtonBar.getWidth()) / 2, screenSize.y - imgButtonBar.getHeight()));
×
358
}
×
359

360
void dskGameInterface::Msg_PaintAfter()
×
361
{
362
    Desktop::Msg_PaintAfter();
×
363

364
    const GameWorldBase& world = worldViewer.GetWorld();
×
365

366
    if(SETTINGS.global.showGFInfo)
×
367
    {
368
        std::array<char, 256> nwf_string;
369
        if(GAMECLIENT.IsReplayModeOn())
×
370
        {
371
            snprintf(nwf_string.data(), nwf_string.size(),
×
372
                     _("(Replay-Mode) Current GF: %u (End at: %u) / GF length: %u ms / NWF length: %u gf (%u ms)"),
373
                     world.GetEvMgr().GetCurrentGF(), GAMECLIENT.GetLastReplayGF(),
×
374
                     GAMECLIENT.GetGFLength() / FramesInfo::milliseconds32_t(1), GAMECLIENT.GetNWFLength(),
×
375
                     GAMECLIENT.GetNWFLength() * GAMECLIENT.GetGFLength() / FramesInfo::milliseconds32_t(1));
×
376
        } else
377
            snprintf(nwf_string.data(), nwf_string.size(),
×
378
                     _("Current GF: %u / GF length: %u ms / NWF length: %u gf (%u ms) /  Ping: %u ms"),
379
                     world.GetEvMgr().GetCurrentGF(), GAMECLIENT.GetGFLength() / FramesInfo::milliseconds32_t(1),
×
380
                     GAMECLIENT.GetNWFLength(),
×
381
                     GAMECLIENT.GetNWFLength() * GAMECLIENT.GetGFLength() / FramesInfo::milliseconds32_t(1),
×
382
                     worldViewer.GetPlayer().ping);
×
383
        NormalFont->Draw(DrawPoint(30, 1), nwf_string.data(), FontStyle{}, COLOR_YELLOW);
×
384
    }
385

386
    // tournament mode?
387
    const unsigned tournamentDuration = GAMECLIENT.GetTournamentModeDuration();
×
388
    if(tournamentDuration)
×
389
    {
390
        unsigned curGF = world.GetEvMgr().GetCurrentGF();
×
391
        std::string tournamentNotice;
×
392
        if(curGF >= tournamentDuration)
×
393
            tournamentNotice = _("Tournament finished");
×
394
        else
395
        {
396
            tournamentNotice =
397
              helpers::format("Tournament mode: %1% remaining", GAMECLIENT.FormatGFTime(tournamentDuration - curGF));
×
398
        }
399
        NormalFont->Draw(DrawPoint(VIDEODRIVER.GetRenderSize().x - 30, 1), tournamentNotice, FontStyle::AlignH::RIGHT,
×
400
                         COLOR_YELLOW);
401
    }
402

403
    // Replaydateianzeige in der linken unteren Ecke
404
    if(GAMECLIENT.IsReplayModeOn())
×
405
    {
406
        NormalFont->Draw(DrawPoint(0, VIDEODRIVER.GetRenderSize().y), GAMECLIENT.GetReplayFilename().string(),
×
407
                         FontStyle::BOTTOM, COLOR_YELLOW);
408
    } else
409
    {
410
        // Laggende Spieler anzeigen in Form von Schnecken
411
        DrawPoint snailPos(VIDEODRIVER.GetRenderSize().x - 70, 35);
×
412
        for(const NWFPlayerInfo& player : nwfInfo_->getPlayerInfos())
×
413
        {
414
            if(player.isLagging)
×
415
            {
416
                LOADER.GetPlayerImage("rttr", 0)->DrawFull(Rect(snailPos, 30, 30), COLOR_WHITE,
×
417
                                                           game_->world_.GetPlayer(player.id).color);
×
418
                snailPos.x -= 40;
×
419
            }
420
        }
421
    }
422

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

426
    // Draw cheating indicator icon (WINTER)
427
    if(cheats_.isCheatModeOn())
×
428
    {
429
        glArchivItem_Bitmap* cheatingImg = LOADER.GetImageN("io", 75);
×
430
        cheatingImg->DrawFull(iconPos);
×
431
        iconPos -= DrawPoint(cheatingImg->getWidth() + 6, 0);
×
432
    }
433

434
    // Draw speed indicator icon
435
    const int speedStep =
436
      static_cast<int>(SPEED_GF_LENGTHS[referenceSpeed] / 10ms) - static_cast<int>(GAMECLIENT.GetGFLength() / 10ms);
×
437

438
    if(speedStep != 0)
×
439
    {
440
        glArchivItem_Bitmap* runnerImg = LOADER.GetImageN("io", 164);
×
441

442
        runnerImg->DrawFull(iconPos);
×
443

444
        if(speedStep != 1)
×
445
        {
446
            std::string multiplier = helpers::toString(std::abs(speedStep));
×
447
            NormalFont->Draw(iconPos - runnerImg->GetOrigin() + DrawPoint(19, 6), multiplier, FontStyle::LEFT,
×
448
                             speedStep > 0 ? COLOR_YELLOW : COLOR_RED);
449
        }
450
        iconPos -= DrawPoint(runnerImg->getWidth() + 4, 0);
×
451
    }
452

453
    // Draw zoom level indicator icon
454
    if(gwv.GetCurrentTargetZoomFactor() != 1.f) //-V550
×
455
    {
456
        glArchivItem_Bitmap* magnifierImg = LOADER.GetImageN("io", 36);
×
457

458
        magnifierImg->DrawFull(iconPos);
×
459

460
        std::string zoom_percent = helpers::toString((int)(gwv.GetCurrentTargetZoomFactor() * 100)) + "%";
×
461
        NormalFont->Draw(iconPos - magnifierImg->GetOrigin() + DrawPoint(9, 7), zoom_percent, FontStyle::CENTER,
×
462
                         COLOR_YELLOW);
463
        iconPos -= DrawPoint(magnifierImg->getWidth() + 4, 0);
×
464
    }
465
}
×
466

467
bool dskGameInterface::Msg_LeftDown(const MouseCoords& mc)
3✔
468
{
469
    DrawPoint btOrig(VIDEODRIVER.GetRenderSize().x / 2 - LOADER.GetImageN("resource", 29)->getWidth() / 2 + 44,
6✔
470
                     VIDEODRIVER.GetRenderSize().y - LOADER.GetImageN("resource", 29)->getHeight() + 4);
9✔
471
    Extent btSize = Extent(37, 32) * 4u;
3✔
472
    if(IsPointInRect(mc.pos, Rect(btOrig, btSize)))
3✔
473
        return false;
×
474

475
    // Start scrolling also on Ctrl + left click
476
    if(VIDEODRIVER.GetModKeyState().ctrl)
3✔
477
    {
478
        Msg_RightDown(mc);
1✔
479
        return true;
1✔
480
    } else if(isScrolling)
2✔
481
        StopScrolling();
2✔
482

483
    // Unterscheiden je nachdem Straäcnbaumodus an oder aus ist
484
    if(road.mode != RoadBuildMode::Disabled)
2✔
485
    {
486
        // in "richtige" Map-Koordinaten Konvertieren, den aktuellen selektierten Punkt
487
        const MapPoint selPt = gwv.GetSelectedPt();
1✔
488

489
        if(selPt == road.point)
1✔
490
        {
491
            // Selektierter Punkt ist der gleiche wie der Straßenpunkt --> Fenster mit Wegbau abbrechen
492
            ShowRoadWindow(mc.pos);
×
493
        } else
494
        {
495
            // altes Roadwindow schließen
496
            WINDOWMANAGER.Close((unsigned)CGI_ROADWINDOW);
1✔
497

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

547
        iwAction::Tabs action_tabs;
1✔
548

549
        const MapPoint cSel = gwv.GetSelectedPt();
1✔
550

551
        // Vielleicht steht hier auch ein Schiff?
552
        if(const noShip* ship = worldViewer.GetShip(cSel))
1✔
553
        {
554
            WINDOWMANAGER.Show(std::make_unique<iwShip>(gwv, GAMECLIENT, ship));
×
555
            return true;
×
556
        }
557

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

601
        action_tabs.watch = true;
1✔
602
        // Unser Land
603
        if(worldViewer.IsOwner(cSel))
1✔
604
        {
605
            const BuildingQuality bq = worldViewer.GetBQ(cSel);
1✔
606
            // Kann hier was gebaut werden?
607
            if(bq >= BuildingQuality::Mine)
1✔
608
            {
609
                action_tabs.build = true;
1✔
610

611
                // Welches Gebäude kann gebaut werden?
612
                switch(bq)
1✔
613
                {
614
                    case BuildingQuality::Mine: action_tabs.build_tabs = iwAction::BuildTab::Mine; break;
×
615
                    case BuildingQuality::Hut: action_tabs.build_tabs = iwAction::BuildTab::Hut; break;
×
616
                    case BuildingQuality::House: action_tabs.build_tabs = iwAction::BuildTab::House; break;
×
617
                    case BuildingQuality::Castle: action_tabs.build_tabs = iwAction::BuildTab::Castle; break;
1✔
618
                    case BuildingQuality::Harbor: action_tabs.build_tabs = iwAction::BuildTab::Harbor; break;
×
619
                    default: break;
×
620
                }
621

622
                if(!worldViewer.GetWorld().IsFlagAround(cSel))
1✔
623
                    action_tabs.setflag = true;
1✔
624

625
                // Prüfen, ob sich Militärgebäude in der Nähe befinden, wenn nein, können auch eigene
626
                // Militärgebäude gebaut werden
627
                enable_military_buildings =
1✔
628
                  !worldViewer.GetWorld().IsMilitaryBuildingNearNode(cSel, worldViewer.GetPlayerId());
1✔
629
            } else if(bq == BuildingQuality::Flag)
×
630
                action_tabs.setflag = true;
×
631
            else if(selObj.GetType() == NodalObjectType::Flag)
×
632
                action_tabs.flag = true;
×
633

634
            if(selObj.GetType() != NodalObjectType::Flag && selObj.GetType() != NodalObjectType::Building)
1✔
635
            {
636
                // Check if there are roads
637
                for(const Direction dir : helpers::EnumRange<Direction>{})
16✔
638
                {
639
                    const PointRoad curRoad = worldViewer.GetVisiblePointRoad(cSel, dir);
6✔
640
                    if(curRoad != PointRoad::None)
6✔
641
                    {
642
                        action_tabs.cutroad = true;
×
643
                        action_tabs.upgradeRoad |= (curRoad == PointRoad::Normal);
×
644
                    }
645
                }
646
            }
647
        }
648
        // evtl ists ein feindliches Militärgebäude, welches NICHT im Nebel liegt?
649
        else if(worldViewer.GetVisibility(cSel) == Visibility::Visible)
×
650
        {
651
            if(selObj.GetType() == NodalObjectType::Building)
×
652
            {
653
                const auto* building = worldViewer.GetWorld().GetSpecObj<noBuilding>(cSel); //-V807
×
654
                BuildingType bt = building->GetBuildingType();
×
655

656
                // Only if trade is enabled
657
                if(worldViewer.GetWorld().GetGGS().isEnabled(AddonId::TRADE))
×
658
                {
659
                    // Allied warehouse? -> Show trade window
660
                    if(BuildingProperties::IsWareHouse(bt) && worldViewer.GetPlayer().IsAlly(building->GetPlayer()))
×
661
                    {
662
                        WINDOWMANAGER.Show(std::make_unique<iwTrade>(*static_cast<const nobBaseWarehouse*>(building),
×
663
                                                                     worldViewer, GAMECLIENT));
×
664
                        return true;
×
665
                    }
666
                }
667

668
                // Ist es ein gewöhnliches Militärgebäude?
669
                if(BuildingProperties::IsMilitary(bt))
×
670
                {
671
                    // Dann darf es nicht neu gebaut sein!
672
                    if(!static_cast<const nobMilitary*>(building)->IsNewBuilt())
×
673
                        action_tabs.attack = true;
×
674
                }
675
                // oder ein HQ oder Hafen?
676
                else if(bt == BuildingType::Headquarters || bt == BuildingType::HarborBuilding)
×
677
                    action_tabs.attack = true;
×
678
                action_tabs.sea_attack =
×
679
                  action_tabs.attack && worldViewer.GetWorld().GetGGS().isEnabled(AddonId::SEA_ATTACK);
×
680
            }
681
        }
682

683
        // Bisheriges Actionfenster schließen, falls es eins gab
684
        // aktuelle Mausposition merken, da diese durch das Schließen verändert werden kann
685
        if(actionwindow)
1✔
686
            actionwindow->Close();
×
687
        VIDEODRIVER.SetMousePos(mc.pos);
1✔
688

689
        ShowActionWindow(action_tabs, cSel, mc.pos, enable_military_buildings);
1✔
690
    }
691

692
    return true;
2✔
693
}
694

695
bool dskGameInterface::Msg_LeftUp(const MouseCoords&)
2✔
696
{
697
    if(isScrolling)
2✔
698
    {
699
        StopScrolling();
1✔
700
        return true;
1✔
701
    }
702
    return false;
1✔
703
}
704

705
bool dskGameInterface::Msg_MouseMove(const MouseCoords& mc)
21✔
706
{
707
    if(!isScrolling)
21✔
708
        return false;
13✔
709

710
    int acceleration = SETTINGS.global.smartCursor ? 2 : 3;
8✔
711

712
    if(SETTINGS.interface.invertMouse)
8✔
713
        acceleration = -acceleration;
1✔
714

715
    gwv.MoveBy((mc.pos - startScrollPt) * acceleration);
8✔
716
    VIDEODRIVER.SetMousePos(startScrollPt);
8✔
717

718
    if(!SETTINGS.global.smartCursor)
8✔
719
        startScrollPt = mc.pos;
×
720
    return true;
8✔
721
}
722

723
bool dskGameInterface::Msg_RightDown(const MouseCoords& mc)
11✔
724
{
725
    StartScrolling(mc.pos);
11✔
726
    return true;
11✔
727
}
728

729
bool dskGameInterface::Msg_RightUp(const MouseCoords& /*mc*/) //-V524
3✔
730
{
731
    if(isScrolling)
3✔
732
        StopScrolling();
3✔
733
    return false;
3✔
734
}
735

736
/**
737
 *  Druck von Spezialtasten auswerten.
738
 */
739
bool dskGameInterface::Msg_KeyDown(const KeyEvent& ke)
15✔
740
{
741
    cheatCommandTracker_.onKeyEvent(ke);
15✔
742

743
    switch(ke.kt)
15✔
744
    {
745
        default: break;
15✔
746
        case KeyType::Return: // Chatfenster öffnen
×
747
            WINDOWMANAGER.Show(std::make_unique<iwChat>(this));
×
748
            return true;
×
749

750
        case KeyType::Space: // Bauqualitäten anzeigen
×
751
            gwv.ToggleShowBQ();
×
752
            return true;
×
753

754
        case KeyType::Left: // Nach Links Scrollen
×
755
            gwv.MoveBy({-30, 0});
×
756
            return true;
×
757
        case KeyType::Right: // Nach Rechts Scrollen
×
758
            gwv.MoveBy({30, 0});
×
759
            return true;
×
760
        case KeyType::Up: // Nach Oben Scrollen
×
761
            gwv.MoveBy({0, -30});
×
762
            return true;
×
763
        case KeyType::Down: // Nach Unten Scrollen
×
764
            gwv.MoveBy({0, 30});
×
765
            return true;
×
766

767
        case KeyType::F2: // Spiel speichern
×
768
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwSave>());
×
769
            return true;
×
770
        case KeyType::F3: // Map debug window/ Multiplayer coordinates
×
771
        {
772
            const bool replayMode = GAMECLIENT.IsReplayModeOn();
×
773
            if(replayMode)
×
774
                DisableFoW(true);
×
775
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwMapDebug>(gwv, game_->world_.IsSinglePlayer() || replayMode));
×
776
            return true;
×
777
        }
778
        case KeyType::F8: // Tastaturbelegung
×
779
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwTextfile>("keyboardlayout.txt", _("Keyboard layout")));
×
780
            return true;
×
781
        case KeyType::F9: // Readme
×
782
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwTextfile>("readme.txt", _("Readme!")));
×
783
            return true;
×
784
        case KeyType::F11: // Music player (midi files)
×
785
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwMusicPlayer>());
×
786
            return true;
×
787
        case KeyType::F12: // Optionsfenster
×
788
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwOptionsWindow>(gwv.GetSoundMgr()));
×
789
            return true;
×
790
    }
791

792
    switch(ke.c)
15✔
793
    {
794
        case '+':
×
795
            if(GAMECLIENT.IsReplayModeOn() || game_->world_.IsSinglePlayer())
×
796
                GAMECLIENT.IncreaseSpeed();
×
797
            return true;
×
798
        case '-':
×
799
            if(GAMECLIENT.IsReplayModeOn() || game_->world_.IsSinglePlayer())
×
800
                GAMECLIENT.DecreaseSpeed();
×
801
            return true;
×
802

803
        case '1':
×
804
        case '2':
805
        case '3': // Spieler umschalten
806
        case '4':
807
        case '5':
808
        case '6':
809
        case '7':
810
        case '8':
811
        {
812
            unsigned playerIdx = ke.c - '1';
×
813
            if(GAMECLIENT.IsReplayModeOn())
×
814
            {
815
                unsigned oldPlayerId = worldViewer.GetPlayerId();
×
816
                GAMECLIENT.ChangePlayerIngame(worldViewer.GetPlayerId(), playerIdx);
×
817
                RTTR_Assert(worldViewer.GetPlayerId() == oldPlayerId || worldViewer.GetPlayerId() == playerIdx);
×
818
            } else if(playerIdx < worldViewer.GetWorld().GetNumPlayers())
×
819
            {
820
                // On mutiplayer this currently asyncs, but as this is a debug feature anyway just disable it there.
821
                // If this should be enabled again, look into the handling/clearing of accumulated GCs
822
                if(game_->world_.IsSinglePlayer())
×
823
                {
824
                    const GamePlayer& player = worldViewer.GetWorld().GetPlayer(playerIdx);
×
825
                    if(player.ps == PlayerState::AI && player.aiInfo.type == AI::Type::Dummy)
×
826
                        GAMECLIENT.RequestSwapToPlayer(playerIdx);
×
827
                }
828
            }
829
            return true;
×
830
        }
831

832
        case 'b': // Zur lezten Position zurückspringen
×
833
            gwv.MoveToLastPosition();
×
834
            return true;
×
835
        case 'v':
×
836
            if(game_->world_.IsSinglePlayer())
×
837
                GAMECLIENT.IncreaseSpeed();
×
838
            return true;
×
839
        case 'c': // Gebäudenamen anzeigen
×
840
            gwv.ToggleShowNames();
×
841
            return true;
×
842
        case 'd': // Replay: FoW an/ausschalten
×
843
            ToggleFoW();
×
844
            return true;
×
845
        case 'h': // Zum HQ springen
×
846
        {
847
            const GamePlayer& player = worldViewer.GetPlayer();
×
848
            // Prüfen, ob dieses überhaupt noch existiert
849
            if(player.GetHQPos().isValid())
×
850
                gwv.MoveToMapPt(player.GetHQPos());
×
851
        }
852
            return true;
×
853
        case 'i': // Show inventory
×
854
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwInventory>(worldViewer.GetPlayer()));
×
855
            return true;
×
856
        case 'j': // GFs überspringen
×
857
            if(game_->world_.IsSinglePlayer() || GAMECLIENT.IsReplayModeOn())
×
858
                WINDOWMANAGER.ToggleWindow(std::make_unique<iwSkipGFs>(gwv));
×
859
            return true;
×
860
        case 'l': // Minimap anzeigen
×
861
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwMinimap>(minimap, gwv));
×
862
            return true;
×
863
        case 'm': // Hauptauswahl
2✔
864
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwMainMenu>(gwv, GAMECLIENT));
2✔
865
            return true;
2✔
866
        case 'n': // Show Post window
×
867
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwPostWindow>(gwv, GetPostBox()));
×
868
            UpdatePostIcon(GetPostBox().GetNumMsgs(), false);
×
869
            return true;
×
870
        case 'p': // Pause
×
871
            GAMECLIENT.TogglePause();
×
872
            return true;
×
873
        case 'q': // Spiel verlassen
×
874
            if(ke.alt)
×
875
                WINDOWMANAGER.ToggleWindow(std::make_unique<iwEndgame>());
×
876
            return true;
×
877
        case 's': // Produktivität anzeigen
×
878
            gwv.ToggleShowProductivity();
×
879
            return true;
×
880
        case 26: // ctrl+z
×
881
            gwv.SetZoomFactor(ZOOM_FACTORS[ZOOM_DEFAULT_INDEX]);
×
NEW
882
            zoomLvl_ = ZOOM_DEFAULT_INDEX;
×
883
            return true;
×
884
        case 'z': // zoom
9✔
885
            if(ke.ctrl)
9✔
886
                zoomLvl_ = ZOOM_DEFAULT_INDEX;
2✔
887
            else if(zoomLvl_ == ZOOM_FACTORS.size() - 1)
7✔
888
                zoomLvl_ = 0;
1✔
889
            else if(zoomLvl_.has_value())
6✔
890
                (*zoomLvl_)++;
5✔
891
            else
892
            {
893
                // Get first level bigger than current zoom
894
                // NOLINTNEXTLINE(readability-qualified-auto)
895
                auto it = std::upper_bound(ZOOM_FACTORS.begin(), ZOOM_FACTORS.end(), gwv.GetCurrentTargetZoomFactor());
1✔
896
                zoomLvl_ =
2✔
897
                  (it == ZOOM_FACTORS.end()) ? ZOOM_FACTORS.size() - 1 : std::distance(ZOOM_FACTORS.begin(), it);
1✔
898
            }
899
            gwv.SetZoomFactor(ZOOM_FACTORS[*zoomLvl_]);
9✔
900
            return true;
9✔
901
        case 'Z': // shift-z, reverse zoom
4✔
902
            if(zoomLvl_ == 0u)
4✔
903
                zoomLvl_ = ZOOM_FACTORS.size() - 1;
1✔
904
            else if(zoomLvl_.has_value())
3✔
905
                (*zoomLvl_)--;
1✔
906
            else
907
            {
908
                // Get last level bigger or equal than current zoom
909
                // NOLINTNEXTLINE(readability-qualified-auto)
910
                auto it = std::lower_bound(ZOOM_FACTORS.begin(), ZOOM_FACTORS.end(), gwv.GetCurrentTargetZoomFactor());
2✔
911
                zoomLvl_ = (it == ZOOM_FACTORS.begin()) ? 0 : std::distance(ZOOM_FACTORS.begin(), it) - 1;
2✔
912
            }
913
            gwv.SetZoomFactor(ZOOM_FACTORS[*zoomLvl_]);
4✔
914
            return true;
4✔
915
    }
916

917
    return false;
×
918
}
919

920
bool dskGameInterface::Msg_WheelUp(const MouseCoords&)
9✔
921
{
922
    WheelZoom(ZOOM_WHEEL_INCREMENT);
9✔
923
    return true;
9✔
924
}
925
bool dskGameInterface::Msg_WheelDown(const MouseCoords&)
10✔
926
{
927
    WheelZoom(-ZOOM_WHEEL_INCREMENT);
10✔
928
    return true;
10✔
929
}
930

931
void dskGameInterface::WheelZoom(const float step)
19✔
932
{
933
    gwv.SetZoomFactor(gwv.GetCurrentTargetZoomFactor() * (1 + step));
19✔
934
    zoomLvl_ = std::nullopt;
19✔
935
}
19✔
936

937
void dskGameInterface::OnBuildingNote(const BuildingNote& note)
×
938
{
939
    switch(note.type)
×
940
    {
941
        case BuildingNote::Constructed:
×
942
        case BuildingNote::Destroyed:
943
        case BuildingNote::Lost:
944
            // Close the related window as the building does not exist anymore
945
            // In "Constructed" this means the buildingsite
946
            WINDOWMANAGER.Close(CGI_BUILDING + MapBase::CreateGUIID(note.pos));
×
947
            break;
×
948
        default: break;
×
949
    }
950
}
×
951

952
void dskGameInterface::Run()
×
953
{
954
    // Reset draw counter of the trees before drawing
955
    noTree::ResetDrawCounter();
×
956

957
    unsigned water_percent;
958
    // Draw mouse only if not on window
959
    bool drawMouse = WINDOWMANAGER.FindWindowAtPos(VIDEODRIVER.GetMousePos()) == nullptr;
×
960
    gwv.Draw(road, actionwindow != nullptr ? actionwindow->GetSelectedPt() : MapPoint::Invalid(), drawMouse,
×
961
             &water_percent);
962

963
    // Indicate that the game is paused by darkening the screen (dark semi-transparent overlay)
964
    if(GAMECLIENT.IsPaused())
×
965
        DrawRectangle(Rect(DrawPoint(0, 0), VIDEODRIVER.GetRenderSize()), COLOR_SHADOW);
×
966
    else
967
    {
968
        // Play ambient sounds if game is not paused
969
        worldViewer.GetSoundMgr().playOceanBrawling(water_percent);
×
970
        worldViewer.GetSoundMgr().playBirdSounds(noTree::QueryDrawCounter());
×
971
    }
972

973
    messenger.Draw();
×
974
}
×
975

976
void dskGameInterface::GI_StartRoadBuilding(const MapPoint startPt, bool waterRoad)
3✔
977
{
978
    // Im Replay keine Straßen bauen
979
    if(GAMECLIENT.IsReplayModeOn())
3✔
980
        return;
×
981

982
    road.mode = waterRoad ? RoadBuildMode::Boat : RoadBuildMode::Normal;
3✔
983
    road.route.clear();
3✔
984
    road.start = road.point = startPt;
3✔
985
    WINDOWMANAGER.SetCursor(Cursor::Remove);
3✔
986
}
987

988
void dskGameInterface::GI_CancelRoadBuilding()
2✔
989
{
990
    if(road.mode == RoadBuildMode::Disabled)
2✔
991
        return;
×
992
    road.mode = RoadBuildMode::Disabled;
2✔
993
    worldViewer.RemoveVisualRoad(road.start, road.route);
2✔
994
    WINDOWMANAGER.SetCursor(isScrolling ? Cursor::Scroll : Cursor::Hand);
2✔
995
}
996

997
bool dskGameInterface::BuildRoadPart(MapPoint& cSel)
3✔
998
{
999
    std::vector<Direction> new_route =
1000
      FindPathForRoad(worldViewer, road.point, cSel, road.mode == RoadBuildMode::Boat, 100);
6✔
1001
    // Weg gefunden?
1002
    if(new_route.empty())
3✔
1003
        return false;
×
1004

1005
    // Test on water way length
1006
    if(road.mode == RoadBuildMode::Boat)
3✔
1007
    {
1008
        unsigned char index = worldViewer.GetWorld().GetGGS().getSelection(AddonId::MAX_WATERWAY_LENGTH);
×
1009

1010
        RTTR_Assert(index < waterwayLengths.size());
×
1011
        const unsigned max_length = waterwayLengths[index];
×
1012

1013
        unsigned length = road.route.size() + new_route.size();
×
1014

1015
        // max_length == 0 heißt beliebig lang, ansonsten
1016
        // Weg zurechtstutzen.
1017
        if(max_length > 0)
×
1018
        {
1019
            while(length > max_length)
×
1020
            {
1021
                new_route.pop_back();
×
1022
                --length;
×
1023
            }
1024
        }
1025
    }
1026

1027
    // Weg (visuell) bauen
1028
    for(const auto dir : new_route)
22✔
1029
    {
1030
        worldViewer.SetVisiblePointRoad(road.point, dir,
19✔
1031
                                        (road.mode == RoadBuildMode::Boat) ? PointRoad::Boat : PointRoad::Normal);
19✔
1032
        worldViewer.RecalcBQForRoad(road.point);
19✔
1033
        road.point = worldViewer.GetWorld().GetNeighbour(road.point, dir);
19✔
1034
    }
1035
    worldViewer.RecalcBQForRoad(road.point);
3✔
1036

1037
    // Zielpunkt updaten (für Wasserweg)
1038
    cSel = road.point;
3✔
1039

1040
    road.route.insert(road.route.end(), new_route.begin(), new_route.end());
3✔
1041

1042
    return true;
3✔
1043
}
1044

1045
unsigned dskGameInterface::GetIdInCurBuildRoad(const MapPoint pt)
1✔
1046
{
1047
    MapPoint curPt = road.start;
1✔
1048
    for(unsigned i = 0; i < road.route.size(); ++i)
2✔
1049
    {
1050
        if(curPt == pt)
2✔
1051
            return i + 1;
1✔
1052

1053
        curPt = worldViewer.GetNeighbour(curPt, road.route[i]);
1✔
1054
    }
1055
    return 0;
×
1056
}
1057

1058
void dskGameInterface::ShowRoadWindow(const Position& mousePos)
×
1059
{
1060
    roadwindow = &WINDOWMANAGER.Show(
×
1061
      std::make_unique<iwRoadWindow>(*this, worldViewer.GetBQ(road.point) != BuildingQuality::Nothing, mousePos), true);
×
1062
}
×
1063

1064
void dskGameInterface::ShowActionWindow(const iwAction::Tabs& action_tabs, MapPoint cSel, const DrawPoint& mousePos,
2✔
1065
                                        const bool enable_military_buildings)
1066
{
1067
    const GameWorldBase& world = worldViewer.GetWorld();
2✔
1068

1069
    iwAction::Params params;
2✔
1070

1071
    // Sind wir am Wasser?
1072
    if(action_tabs.setflag)
2✔
1073
    {
1074
        auto isWater = [](const auto& desc) { return desc.kind == TerrainKind::Water; };
6✔
1075
        if(world.HasTerrain(cSel, isWater))
1✔
1076
            params = iwAction::FlagType::WaterFlag;
×
1077
    }
1078

1079
    // Wenn es einen Flaggen-Tab gibt, dann den Flaggentyp herausfinden und die Art des Fensters entsprechende setzen
1080
    if(action_tabs.flag)
2✔
1081
    {
1082
        if(world.GetNO(world.GetNeighbour(cSel, Direction::NorthWest))->GetGOT() == GO_Type::NobHq)
×
1083
            params = iwAction::FlagType::HQ;
×
1084
        else if(world.GetNO(cSel)->GetType() == NodalObjectType::Flag)
×
1085
        {
1086
            if(world.GetSpecObj<noFlag>(cSel)->GetFlagType() == FlagType::Water)
×
1087
                params = iwAction::FlagType::WaterFlag;
×
1088
        }
1089
    }
1090

1091
    // Angriffstab muss wissen, wieviel Soldaten maximal entsendet werden können
1092
    if(action_tabs.attack)
2✔
1093
    {
1094
        params = worldViewer.GetNumSoldiersForAttack(cSel);
×
1095
    }
1096

1097
    actionwindow = &WINDOWMANAGER.Show(
2✔
1098
      std::make_unique<iwAction>(*this, gwv, action_tabs, cSel, mousePos, params, enable_military_buildings), true);
2✔
1099
}
2✔
1100

1101
void dskGameInterface::OnChatCommand(const std::string& cmd)
×
1102
{
1103
    cheatCommandTracker_.onChatCommand(cmd);
×
1104

1105
    if(cmd == "surrender")
×
1106
        GAMECLIENT.Surrender();
×
1107
    else if(cmd == "async")
×
1108
        (void)RANDOM.Rand(RANDOM_CONTEXT2(0), 255);
×
1109
    else if(cmd == "segfault")
×
1110
    {
1111
        char* x = nullptr;
×
1112
        *x = 1; //-V522 // NOLINT
×
1113
    } else if(cmd == "reload")
×
1114
    {
1115
        WorldDescription newDesc;
×
1116
        GameDataLoader gdLoader(newDesc);
×
1117
        if(gdLoader.Load())
×
1118
        {
1119
            const_cast<GameWorld&>(game_->world_).GetDescriptionWriteable() = newDesc;
×
1120
            worldViewer.InitTerrainRenderer();
×
1121
        }
1122
    }
1123
}
×
1124

1125
void dskGameInterface::GI_BuildRoad()
×
1126
{
1127
    if(GAMECLIENT.BuildRoad(road.start, road.mode == RoadBuildMode::Boat, road.route))
×
1128
    {
1129
        road.mode = RoadBuildMode::Disabled;
×
1130
        WINDOWMANAGER.SetCursor(Cursor::Hand);
×
1131
    }
1132
}
×
1133

1134
void dskGameInterface::Msg_WindowClosed(IngameWindow& wnd)
2✔
1135
{
1136
    if(actionwindow == &wnd)
2✔
1137
        actionwindow = nullptr;
1✔
1138
    else if(roadwindow == &wnd)
1✔
1139
        roadwindow = nullptr;
×
1140
}
2✔
1141

1142
void dskGameInterface::GI_FlagDestroyed(const MapPoint pt)
×
1143
{
1144
    // Im Wegbaumodus und haben wir von hier eine Flagge gebaut?
1145
    if(road.mode != RoadBuildMode::Disabled && road.start == pt)
×
1146
    {
1147
        GI_CancelRoadBuilding();
×
1148
    }
1149

1150
    // Evtl Actionfenster schließen, da sich das ja auch auf diese Flagge bezieht
1151
    if(actionwindow)
×
1152
    {
1153
        if(actionwindow->GetSelectedPt() == pt)
×
1154
            actionwindow->Close();
×
1155
    }
1156
}
×
1157

1158
void dskGameInterface::CI_PlayerLeft(const unsigned playerId)
×
1159
{
1160
    // Info-Meldung ausgeben
1161
    std::string text =
1162
      helpers::format(_("Player '%s' left the game!"), worldViewer.GetWorld().GetPlayer(playerId).name);
×
1163
    messenger.AddMessage("", 0, ChatDestination::System, text, COLOR_RED);
×
1164
    // Im Spiel anzeigen, dass die KI das Spiel betreten hat
1165
    text = helpers::format(_("Player '%s' joined the game!"), "KI");
×
1166
    messenger.AddMessage("", 0, ChatDestination::System, text, COLOR_GREEN);
×
1167
}
×
1168

1169
void dskGameInterface::CI_GGSChanged(const GlobalGameSettings& /*ggs*/)
×
1170
{
1171
    // TODO: print what has changed
1172
    const std::string text = helpers::format(_("Note: Game settings changed by the server%s"), "");
×
1173
    messenger.AddMessage("", 0, ChatDestination::System, text);
×
1174
}
×
1175

1176
void dskGameInterface::CI_Chat(const unsigned playerId, const ChatDestination cd, const std::string& msg)
×
1177
{
1178
    messenger.AddMessage(worldViewer.GetWorld().GetPlayer(playerId).name,
×
1179
                         worldViewer.GetWorld().GetPlayer(playerId).color, cd, msg);
×
1180
}
×
1181

1182
void dskGameInterface::CI_Async(const std::string& checksums_list)
×
1183
{
1184
    messenger.AddMessage("", 0, ChatDestination::System,
×
1185
                         _("The Game is not in sync. Checksums of some players don't match."), COLOR_RED);
1186
    messenger.AddMessage("", 0, ChatDestination::System, checksums_list, COLOR_YELLOW);
×
1187
    messenger.AddMessage("", 0, ChatDestination::System, _("A auto-savegame is created..."), COLOR_RED);
×
1188
}
×
1189

1190
void dskGameInterface::CI_ReplayAsync(const std::string& msg)
×
1191
{
1192
    messenger.AddMessage("", 0, ChatDestination::System, msg, COLOR_RED);
×
1193
}
×
1194

1195
void dskGameInterface::CI_ReplayEndReached(const std::string& msg)
×
1196
{
1197
    messenger.AddMessage("", 0, ChatDestination::System, msg, COLOR_BLUE);
×
1198
}
×
1199

1200
void dskGameInterface::CI_GamePaused()
×
1201
{
1202
    messenger.AddMessage(_("SYSTEM"), COLOR_GREY, ChatDestination::System, _("Game was paused."));
×
1203
}
×
1204

1205
void dskGameInterface::CI_GameResumed()
×
1206
{
1207
    messenger.AddMessage(_("SYSTEM"), COLOR_GREY, ChatDestination::System, _("Game was resumed."));
×
1208
}
×
1209

1210
void dskGameInterface::CI_Error(const ClientError ce)
×
1211
{
1212
    messenger.AddMessage("", 0, ChatDestination::System, ClientErrorToStr(ce), COLOR_RED);
×
1213
    GAMECLIENT.SetPause(true);
×
1214
}
×
1215

1216
/**
1217
 *  Status: Verbindung verloren.
1218
 */
1219
void dskGameInterface::LC_Status_ConnectionLost()
×
1220
{
1221
    messenger.AddMessage("", 0, ChatDestination::System, _("Lost connection to lobby!"), COLOR_RED);
×
1222
}
×
1223

1224
/**
1225
 *  (Lobby-)Status: Benutzerdefinierter Fehler
1226
 */
1227
void dskGameInterface::LC_Status_Error(const std::string& error)
×
1228
{
1229
    messenger.AddMessage("", 0, ChatDestination::System, error, COLOR_RED);
×
1230
}
×
1231

1232
void dskGameInterface::CI_PlayersSwapped(const unsigned player1, const unsigned player2)
×
1233
{
1234
    // Meldung anzeigen
1235
    std::string text = "Player '" + worldViewer.GetWorld().GetPlayer(player1).name + "' switched to player '"
×
1236
                       + worldViewer.GetWorld().GetPlayer(player2).name + "'";
×
1237
    messenger.AddMessage("", 0, ChatDestination::System, text, COLOR_YELLOW);
×
1238

1239
    // Sichtbarkeiten und Minimap neu berechnen, wenn wir ein von den beiden Spielern sind
1240
    const unsigned localPlayerId = worldViewer.GetPlayerId();
×
1241
    if(player1 == localPlayerId || player2 == localPlayerId)
×
1242
    {
1243
        worldViewer.ChangePlayer(player1 == localPlayerId ? player2 : player1);
×
1244
        // Set visual settings back to the actual ones
1245
        GAMECLIENT.ResetVisualSettings();
×
1246
        minimap.UpdateAll();
×
1247
        InitPlayer();
×
1248
    }
1249
}
×
1250

1251
/**
1252
 *  Wenn ein Spieler verloren hat
1253
 */
1254
void dskGameInterface::GI_PlayerDefeated(const unsigned playerId)
×
1255
{
1256
    const std::string text =
1257
      helpers::format(_("Player '%s' was defeated!"), worldViewer.GetWorld().GetPlayer(playerId).name);
×
1258
    messenger.AddMessage("", 0, ChatDestination::System, text, COLOR_ORANGE);
×
1259

1260
    /// Lokaler Spieler?
1261
    if(playerId == worldViewer.GetPlayerId())
×
1262
    {
1263
        /// Sichtbarkeiten neu berechnen
1264
        worldViewer.RecalcAllColors();
×
1265
        // Minimap updaten
1266
        minimap.UpdateAll();
×
1267
    }
1268
}
×
1269

1270
void dskGameInterface::GI_UpdateMinimap(const MapPoint pt)
×
1271
{
1272
    // Minimap Bescheid sagen
1273
    minimap.UpdateNode(pt);
×
1274
}
×
1275

1276
void dskGameInterface::GI_UpdateMapVisibility()
×
1277
{
1278
    // recalculate visibility
1279
    worldViewer.RecalcAllColors();
×
1280
    // update minimap
1281
    minimap.UpdateAll();
×
1282
}
×
1283

1284
/**
1285
 *  Bündnisvertrag wurde abgeschlossen oder abgebrochen --> Minimap updaten
1286
 */
1287
void dskGameInterface::GI_TreatyOfAllianceChanged(unsigned playerId)
×
1288
{
1289
    // Nur wenn Team-Sicht aktiviert ist, können sihc die Sichtbarkeiten auch ändern
1290
    if(playerId == worldViewer.GetPlayerId() && worldViewer.GetWorld().GetGGS().teamView)
×
1291
    {
1292
        /// Sichtbarkeiten neu berechnen
1293
        worldViewer.RecalcAllColors();
×
1294
        // Minimap updaten
1295
        minimap.UpdateAll();
×
1296
    }
1297
}
×
1298

1299
/**
1300
 *  Baut Weg zurück von Ende bis zu start_id
1301
 */
1302
void dskGameInterface::DemolishRoad(const unsigned start_id)
1✔
1303
{
1304
    RTTR_Assert(start_id > 0);
1✔
1305
    for(unsigned i = road.route.size(); i >= start_id; --i)
6✔
1306
    {
1307
        MapPoint t = road.point;
5✔
1308
        road.point = worldViewer.GetWorld().GetNeighbour(road.point, road.route[i - 1] + 3u);
5✔
1309
        worldViewer.SetVisiblePointRoad(road.point, road.route[i - 1], PointRoad::None);
5✔
1310
        worldViewer.RecalcBQForRoad(t);
5✔
1311
    }
1312

1313
    road.route.resize(start_id - 1);
1✔
1314
}
1✔
1315

1316
/**
1317
 *  Updatet das Post-Icon mit der Nachrichtenanzahl und der Taube
1318
 */
1319
void dskGameInterface::UpdatePostIcon(const unsigned postmessages_count, bool showPigeon)
6✔
1320
{
1321
    // Taube setzen oder nicht (Post)
1322
    if(postmessages_count == 0 || !showPigeon)
6✔
1323
        GetCtrl<ctrlImageButton>(3)->SetImage(LOADER.GetImageN("io", 62));
12✔
1324
    else
1325
        GetCtrl<ctrlImageButton>(3)->SetImage(LOADER.GetImageN("io", 59));
×
1326

1327
    // und Anzahl der Postnachrichten aktualisieren
1328
    if(postmessages_count > 0)
6✔
1329
    {
1330
        GetCtrl<ctrlText>(ID_txtNumMsg)->SetText(std::to_string(postmessages_count));
×
1331
    } else
1332
        GetCtrl<ctrlText>(ID_txtNumMsg)->SetText("");
6✔
1333
}
6✔
1334

1335
/**
1336
 *  Neue Post-Nachricht eingetroffen
1337
 */
1338
void dskGameInterface::NewPostMessage(const PostMsg& msg, const unsigned msgCt)
×
1339
{
1340
    UpdatePostIcon(msgCt, true);
×
1341
    SoundEffect soundEffect = msg.GetSoundEffect();
×
1342
    switch(soundEffect)
×
1343
    {
1344
        case SoundEffect::Pidgeon: LOADER.GetSoundN("sound", 114)->Play(100, false); break;
×
1345
        case SoundEffect::Fanfare: LOADER.GetSoundN("sound", 110)->Play(100, false);
×
1346
    }
1347
}
×
1348

1349
/**
1350
 *  Es wurde eine Postnachricht vom Spieler gelöscht
1351
 */
1352
void dskGameInterface::PostMessageDeleted(const unsigned msgCt)
×
1353
{
1354
    UpdatePostIcon(msgCt, false);
×
1355
}
×
1356

1357
/**
1358
 *  Ein Spieler hat das Spiel gewonnen.
1359
 */
1360
void dskGameInterface::GI_Winner(const unsigned playerId)
×
1361
{
1362
    const std::string name = worldViewer.GetWorld().GetPlayer(playerId).name;
×
1363
    const std::string text = (boost::format(_("Player '%s' is the winner!")) % name).str();
×
1364
    messenger.AddMessage("", 0, ChatDestination::System, text, COLOR_ORANGE);
×
1365
    WINDOWMANAGER.Show(std::make_unique<iwVictory>(std::vector<std::string>(1, name)));
×
1366
}
×
1367

1368
/**
1369
 *  Ein Team hat das Spiel gewonnen.
1370
 */
1371
void dskGameInterface::GI_TeamWinner(const unsigned playerMask)
×
1372
{
1373
    std::vector<std::string> winners;
×
1374
    const GameWorldBase& world = worldViewer.GetWorld();
×
1375
    for(unsigned i = 0; i < world.GetNumPlayers(); i++)
×
1376
    {
1377
        if(playerMask & (1 << i))
×
1378
            winners.push_back(world.GetPlayer(i).name);
×
1379
    }
1380
    const std::string text =
1381
      (boost::format(_("%1% are the winners!")) % helpers::join(winners, ", ", _(" and "))).str();
×
1382
    messenger.AddMessage("", 0, ChatDestination::System, text, COLOR_ORANGE);
×
1383
    WINDOWMANAGER.Show(std::make_unique<iwVictory>(winners));
×
1384
}
×
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