GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/windows/questswindow.cpp Lines: 64 252 25.4 %
Date: 2018-06-18 21:15:20 Branches: 73 363 20.1 %

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


8
    mQuestsListBox(CREATEWIDGETR(ExtendedListBox,
69
        this, mQuestsModel, "extendedlistbox.xml", 13)),
70
2
    mQuestScrollArea(new ScrollArea(this, mQuestsListBox,
71

8
        fromBool(getOptionBool("showlistbackground", false), Opaque),
72

4
        "quests_list_background.xml")),
73

2
    mItemLinkHandler(new ItemLinkHandler),
74
    mText(new BrowserBox(this, Opaque_true,
75

2
        "browserbox.xml")),
76
2
    mTextScrollArea(new ScrollArea(this, mText,
77

8
        fromBool(getOptionBool("showtextbackground", false), Opaque),
78

4
        "quests_text_background.xml")),
79
    // TRANSLATORS: quests window button
80

4
    mCloseButton(new Button(this, _("Close"), "close", BUTTON_SKIN, this)),
81

14
    mCompleteIcon(Theme::getImageFromThemeXml("complete_icon.xml", "")),
82

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

8
    mNewQuestEffectId(paths.getIntValue("newQuestEffectId")),
91

8
    mCompleteQuestEffectId(paths.getIntValue("completeQuestEffectId")),
92




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

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

6
}