GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/windows/questswindow.cpp Lines: 64 252 25.4 %
Date: 2021-03-17 Branches: 73 371 19.7 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2012-2019  The ManaPlus Developers
4
 *  Copyright (C) 2019-2021  Andrei Karas
5
 *
6
 *  This file is part of The ManaPlus Client.
7
 *
8
 *  This program is free software; you can redistribute it and/or modify
9
 *  it under the terms of the GNU General Public License as published by
10
 *  the Free Software Foundation; either version 2 of the License, or
11
 *  any later version.
12
 *
13
 *  This program is distributed in the hope that it will be useful,
14
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 *  GNU General Public License for more details.
17
 *
18
 *  You should have received a copy of the GNU General Public License
19
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
 */
21
22
#include "gui/windows/questswindow.h"
23
24
#include "actormanager.h"
25
#include "configuration.h"
26
#include "effectmanager.h"
27
28
#include "being/localplayer.h"
29
30
#include "enums/gui/layouttype.h"
31
32
#include "gui/gui.h"
33
34
#include "gui/fonts/font.h"
35
36
#include "gui/models/questsmodel.h"
37
38
#include "gui/windows/setupwindow.h"
39
40
#include "gui/widgets/button.h"
41
#include "gui/widgets/browserbox.h"
42
#include "gui/widgets/containerplacer.h"
43
#include "gui/widgets/createwidget.h"
44
#include "gui/widgets/layout.h"
45
#include "gui/widgets/extendedlistbox.h"
46
#include "gui/widgets/itemlinkhandler.h"
47
#include "gui/widgets/scrollarea.h"
48
49
#include "utils/delete2.h"
50
#include "utils/foreach.h"
51
#include "utils/gettext.h"
52
53
#include "resources/questeffect.h"
54
#include "resources/questitem.h"
55
56
#include "resources/db/questdb.h"
57
58
#include "resources/map/map.h"
59
60
#include "debug.h"
61
62
QuestsWindow *questsWindow = nullptr;
63
64
1
QuestsWindow::QuestsWindow() :
65
    // TRANSLATORS: quests window name
66
1
    Window(_("Quests"), Modal_false, nullptr, "quests.xml"),
67
    ActionListener(),
68
2
    mQuestsModel(new QuestsModel),
69


4
    mQuestsListBox(CREATEWIDGETR(ExtendedListBox,
70
        this, mQuestsModel, "extendedlistbox.xml", 13)),
71
1
    mQuestScrollArea(new ScrollArea(this, mQuestsListBox,
72

4
        fromBool(getOptionBool("showlistbackground", false), Opaque),
73

2
        "quests_list_background.xml")),
74

1
    mItemLinkHandler(new ItemLinkHandler),
75
    mText(new BrowserBox(this, Opaque_true,
76

1
        "browserbox.xml")),
77
1
    mTextScrollArea(new ScrollArea(this, mText,
78

4
        fromBool(getOptionBool("showtextbackground", false), Opaque),
79

2
        "quests_text_background.xml")),
80
    // TRANSLATORS: quests window button
81

2
    mCloseButton(new Button(this, _("Close"), "close", BUTTON_SKIN, this)),
82

7
    mCompleteIcon(Theme::getImageFromThemeXml("complete_icon.xml", "")),
83

7
    mIncompleteIcon(Theme::getImageFromThemeXml("incomplete_icon.xml", "")),
84
    mMapEffects(),
85
    mVars(nullptr),
86
    mQuests(nullptr),
87
    mAllEffects(nullptr),
88
    mNpcEffects(),
89
    mQuestLinks(),
90
    mQuestReverseLinks(),
91

4
    mNewQuestEffectId(paths.getIntValue("newQuestEffectId")),
92

4
    mCompleteQuestEffectId(paths.getIntValue("completeQuestEffectId")),
93



43
    mMap(nullptr)
94
{
95
5
    setWindowName("Quests");
96
1
    setResizable(true);
97
1
    setCloseButton(true);
98
1
    setStickyButtonLock(true);
99
2
    setSaveVisible(true);
100
101
1
    setDefaultSize(400, 350, ImagePosition::RIGHT, 0, 0);
102
1
    setMinWidth(310);
103
1
    setMinHeight(220);
104
105
1
    if (setupWindow != nullptr)
106
        setupWindow->registerWindowForReset(this);
107
108
5
    mQuestsListBox->setActionEventId("select");
109
1
    mQuestsListBox->addActionListener(this);
110
111
1
    mQuestScrollArea->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
112
2
    mText->setOpaque(Opaque_false);
113
1
    mText->setLinkHandler(mItemLinkHandler);
114
1
    mTextScrollArea->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
115
1
    mQuestsListBox->setWidth(500);
116


2
    if ((gui == nullptr) || gui->getNpcFont()->getHeight() < 20)
117
1
        mQuestsListBox->setRowHeight(20);
118
    else
119
        mQuestsListBox->setRowHeight(gui->getNpcFont()->getHeight());
120
121
1
    ContainerPlacer placer(nullptr, nullptr);
122
1
    placer = getPlacer(0, 0);
123
124
2
    placer(0, 0, mQuestScrollArea, 4, 3).setPadding(3);
125
2
    placer(4, 0, mTextScrollArea, 4, 3).setPadding(3);
126
1
    placer(7, 3, mCloseButton, 1, 1);
127
128
1
    Layout &layout = getLayout();
129
1
    layout.setRowHeight(0, LayoutType::SET);
130
131
1
    loadWindowState();
132
2
    enableVisibleSound(true);
133
1
    QuestDb::load();
134
1
    mVars = QuestDb::getVars();
135
1
    mQuests = QuestDb::getQuests();
136
1
    mAllEffects = QuestDb::getAllEffects();
137
1
}
138
139
8
QuestsWindow::~QuestsWindow()
140
{
141
2
    delete2(mQuestsModel)
142
143
1
    QuestDb::unload();
144
145
1
    delete2(mItemLinkHandler)
146
2
    mQuestLinks.clear();
147
2
    mQuestReverseLinks.clear();
148
1
    if (mCompleteIcon != nullptr)
149
    {
150
1
        mCompleteIcon->decRef();
151
1
        mCompleteIcon = nullptr;
152
    }
153
1
    if (mIncompleteIcon != nullptr)
154
    {
155
1
        mIncompleteIcon->decRef();
156
1
        mIncompleteIcon = nullptr;
157
    }
158
2
}
159
160
void QuestsWindow::action(const ActionEvent &event)
161
{
162
    const std::string &eventId = event.getId();
163
    if (eventId == "select")
164
    {
165
        const int id = mQuestsListBox->getSelected();
166
        if (id < 0)
167
            return;
168
        showQuest(mQuestLinks[id]);
169
    }
170
    else if (eventId == "close")
171
    {
172
        setVisible(Visible_false);
173
    }
174
}
175
176
void QuestsWindow::updateQuest(const int var,
177
                               const int val1,
178
                               const int val2,
179
                               const int val3,
180
                               const int time1)
181
{
182
    (*mVars)[var] = QuestVar(val1, val2, val3, time1);
183
}
184
185
void QuestsWindow::rebuild(const bool playSound)
186
{
187
    mQuestsModel->clear();
188
    mQuestLinks.clear();
189
    mQuestReverseLinks.clear();
190
    StringVect &names = mQuestsModel->getNames();
191
    STD_VECTOR<Image*> &images = mQuestsModel->getImages();
192
    STD_VECTOR<QuestItem*> complete;
193
    STD_VECTOR<QuestItem*> incomplete;
194
    STD_VECTOR<QuestItem*> hidden;
195
    int updatedQuest = -1;
196
    int newCompleteStatus = -1;
197
198
    FOR_EACHP (NpcQuestVarMapCIter, it, mVars)
199
    {
200
        const int var = (*it).first;
201
        const QuestVar &val = (*it).second;
202
        const STD_VECTOR<QuestItem*> &quests = (*mQuests)[var];
203
        FOR_EACH (STD_VECTOR<QuestItem*>::const_iterator, it2, quests)
204
        {
205
            if (*it2 == nullptr)
206
                continue;
207
            QuestItem *const quest = *it2;
208
            // complete quest
209
            if (quest->complete.find(val.var1) != quest->complete.end())
210
            {
211
                complete.push_back(quest);
212
            }
213
            // incomplete quest
214
            else if (quest->incomplete.find(val.var1) !=
215
                     quest->incomplete.end())
216
            {
217
                incomplete.push_back(quest);
218
            }
219
            // hidden quest
220
            else
221
            {
222
                hidden.push_back(quest);
223
            }
224
        }
225
    }
226
227
    int k = 0;
228
229
    for (STD_VECTOR<QuestItem*>::const_iterator it = complete.begin(),
230
        it_end = complete.end(); it != it_end; ++ it, k ++)
231
    {
232
        QuestItem *const quest = *it;
233
        if (quest->completeFlag == 0 || (quest->broken
234
            && quest->completeFlag == -1))
235
        {
236
            updatedQuest = k;
237
            newCompleteStatus = 1;
238
        }
239
        quest->completeFlag = 1;
240
        mQuestLinks.push_back(quest);
241
        mQuestReverseLinks[quest->var] = k;
242
        names.push_back(quest->name);
243
        if (mCompleteIcon != nullptr)
244
        {
245
            mCompleteIcon->incRef();
246
            images.push_back(mCompleteIcon);
247
        }
248
        else
249
        {
250
            images.push_back(nullptr);
251
        }
252
    }
253
254
    for (STD_VECTOR<QuestItem*>::const_iterator it = incomplete.begin(),
255
        it_end = incomplete.end(); it != it_end; ++ it, k ++)
256
    {
257
        QuestItem *const quest = *it;
258
        if (quest->completeFlag == -1)
259
        {
260
            updatedQuest = k;
261
            newCompleteStatus = 0;
262
        }
263
        quest->completeFlag = 0;
264
        mQuestLinks.push_back(quest);
265
        mQuestReverseLinks[quest->var] = k;
266
        names.push_back(quest->name);
267
        if (mIncompleteIcon != nullptr)
268
        {
269
            mIncompleteIcon->incRef();
270
            images.push_back(mIncompleteIcon);
271
        }
272
        else
273
        {
274
            images.push_back(nullptr);
275
        }
276
    }
277
278
    FOR_EACH (STD_VECTOR<QuestItem*>::const_iterator, it, hidden)
279
        (*it)->completeFlag = -1;
280
281
    if (updatedQuest == -1 || updatedQuest >= CAST_S32(
282
        mQuestLinks.size()))
283
    {
284
        updatedQuest = CAST_S32(mQuestLinks.size() - 1);
285
    }
286
    if (updatedQuest >= 0)
287
    {
288
        mQuestsListBox->setSelected(updatedQuest);
289
        showQuest(mQuestLinks[updatedQuest]);
290
        if (playSound && (effectManager != nullptr))
291
        {
292
            switch (newCompleteStatus)
293
            {
294
                case 0:
295
                    effectManager->trigger(mNewQuestEffectId, localPlayer, 0);
296
                    break;
297
                case 1:
298
                    effectManager->trigger(mCompleteQuestEffectId,
299
                        localPlayer,
300
                        0);
301
                    break;
302
                default:
303
                    break;
304
            }
305
        }
306
    }
307
    updateEffects();
308
}
309
310
void QuestsWindow::showQuest(const QuestItem *const quest)
311
{
312
    if (quest == nullptr)
313
        return;
314
315
    const STD_VECTOR<QuestItemText> &texts = quest->texts;
316
    const QuestVar &var = (*mVars)[quest->var];
317
    const std::string var1 = toString(var.var1);
318
    const std::string var2 = toString(var.var2);
319
    const std::string var3 = toString(var.var3);
320
    const std::string timeStr = timeDiffToString(var.time1);
321
    mText->clearRows();
322
    FOR_EACH (STD_VECTOR<QuestItemText>::const_iterator, it, texts)
323
    {
324
        const QuestItemText &data = *it;
325
        std::string text = data.text;
326
        replaceAll(text, "{@@var1}", var1);
327
        replaceAll(text, "{@@var2}", var2);
328
        replaceAll(text, "{@@var3}", var3);
329
        replaceAll(text, "{@@time}", timeStr);
330
        switch (data.type)
331
        {
332
            case QuestType::TEXT:
333
            default:
334
                mText->addRow(text,
335
                    false);
336
                break;
337
            case QuestType::NAME:
338
                mText->addRow(std::string("[").append(text).append("]"),
339
                    false);
340
                break;
341
            case QuestType::REWARD:
342
                mText->addRow(std::string(
343
                    // TRANSLATORS: quest reward
344
                    _("Reward:")).append(
345
                    " ").append(
346
                    text),
347
                    false);
348
                break;
349
            case QuestType::GIVER:
350
                mText->addRow(std::string(
351
                    // TRANSLATORS: quest giver name
352
                    _("Quest Giver:")).append(
353
                    " ").append(
354
                    text),
355
                    false);
356
                break;
357
            case QuestType::NPC:
358
                mText->addRow(std::string(
359
                    // TRANSLATORS: quest npc name
360
                    _("Npc:")).append(
361
                    " ").append(
362
                    text),
363
                    false);
364
                break;
365
            case QuestType::COORDINATES:
366
                mText->addRow(std::string(
367
                    strprintf("%s [@@=navigate %s %s|%s@@]",
368
                    // TRANSLATORS: quest coordinates
369
                    _("Coordinates:"),
370
                    data.data1.c_str(),
371
                    data.data2.c_str(),
372
                    text.c_str())),
373
                    false);
374
                break;
375
        }
376
    }
377
    mText->updateHeight();
378
}
379
380
void QuestsWindow::setMap(const Map *const map)
381
{
382
    if (mMap != map)
383
    {
384
        mMap = map;
385
        mMapEffects.clear();
386
        if (mMap == nullptr)
387
            return;
388
389
        const std::string name = mMap->getProperty("shortName",
390
            std::string());
391
        FOR_EACHP (STD_VECTOR<QuestEffect*>::const_iterator, it,  mAllEffects)
392
        {
393
            const QuestEffect *const effect = *it;
394
            if ((effect != nullptr) && name == effect->map)
395
                mMapEffects.push_back(effect);
396
        }
397
        updateEffects();
398
    }
399
}
400
401
void QuestsWindow::updateEffects()
402
{
403
    NpcQuestEffectMap oldNpc = mNpcEffects;
404
    mNpcEffects.clear();
405
406
    FOR_EACH (STD_VECTOR<const QuestEffect*>::const_iterator,
407
              it,  mMapEffects)
408
    {
409
        const QuestEffect *const effect = *it;
410
        if (effect != nullptr)
411
        {
412
            const NpcQuestVarMapCIter varIt = mVars->find(effect->var);
413
            if (varIt != mVars->end())
414
            {
415
                const std::set<int> &vals = effect->values;
416
                if (vals.find((*mVars)[effect->var].var1) != vals.end())
417
                    mNpcEffects[effect->id] = effect;
418
            }
419
        }
420
    }
421
    if (actorManager == nullptr)
422
        return;
423
424
    std::set<BeingTypeId> removeEffects;
425
    std::map<BeingTypeId, int> addEffects;
426
427
    // for old effects
428
    FOR_EACH (NpcQuestEffectMapCIter, it, oldNpc)
429
    {
430
        const BeingTypeId id = (*it).first;
431
        const QuestEffect *const effect = (*it).second;
432
433
        const NpcQuestEffectMapCIter itNew = mNpcEffects.find(id);
434
        if (itNew == mNpcEffects.end())
435
        {   // in new list no effect for this npc
436
            removeEffects.insert(id);
437
        }
438
        else
439
        {   // in new list exists effect for this npc
440
            const QuestEffect *const newEffect = (*itNew).second;
441
            if (effect != newEffect)
442
            {   // new effects is not equal to old effect
443
                addEffects[id] = newEffect->effectId;
444
                removeEffects.insert(id);
445
            }
446
        }
447
    }
448
449
    // for new effects
450
    FOR_EACH (NpcQuestEffectMapCIter, it, mNpcEffects)
451
    {
452
        const BeingTypeId id = (*it).first;
453
        const QuestEffect *const effect = (*it).second;
454
455
        const NpcQuestEffectMapCIter itNew = oldNpc.find(id);
456
        // check if old effect was not present
457
        if (itNew == oldNpc.end())
458
            addEffects[id] = effect->effectId;
459
    }
460
    if (!removeEffects.empty() || !addEffects.empty())
461
        actorManager->updateEffects(addEffects, removeEffects);
462
}
463
464
void QuestsWindow::addEffect(Being *const being)
465
{
466
    if (being == nullptr)
467
        return;
468
    const BeingTypeId id = being->getSubType();
469
    const std::map<BeingTypeId, const QuestEffect*>::const_iterator
470
        it = mNpcEffects.find(id);
471
    if (it != mNpcEffects.end())
472
    {
473
        const QuestEffect *const effect = (*it).second;
474
        if (effect != nullptr)
475
            being->addSpecialEffect(effect->effectId);
476
    }
477
}
478
479
void QuestsWindow::selectQuest(const int varId)
480
{
481
    std::map<int, int>::const_iterator it = mQuestReverseLinks.find(varId);
482
    if (it == mQuestReverseLinks.end())
483
        return;
484
    if (mVisible == Visible_false)
485
        setVisible(Visible_true);
486
    const int listPos = (*it).second;
487
    if (listPos < 0)
488
        return;
489
    showQuest(mQuestLinks[listPos]);
490
    mQuestsListBox->setSelected(listPos);
491
    requestMoveToTop();
492

3
}