GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/windows/npcdialog.cpp Lines: 139 699 19.9 %
Date: 2017-11-29 Branches: 156 795 19.6 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2004-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2017  The ManaPlus Developers
6
 *
7
 *  This file is part of The ManaPlus Client.
8
 *
9
 *  This program is free software; you can redistribute it and/or modify
10
 *  it under the terms of the GNU General Public License as published by
11
 *  the Free Software Foundation; either version 2 of the License, or
12
 *  any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU General Public License
20
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
#include "gui/windows/npcdialog.h"
24
25
#include "actormanager.h"
26
#include "configuration.h"
27
#include "settings.h"
28
#include "soundmanager.h"
29
30
#include "const/sound.h"
31
32
#include "being/being.h"
33
#include "being/playerinfo.h"
34
35
#include "enums/gui/layouttype.h"
36
37
#include "gui/gui.h"
38
#include "gui/viewport.h"
39
40
#include "gui/fonts/font.h"
41
42
#include "gui/popups/popupmenu.h"
43
44
#include "gui/windows/cutinwindow.h"
45
#include "gui/windows/inventorywindow.h"
46
47
#include "gui/widgets/browserbox.h"
48
#include "gui/widgets/button.h"
49
#include "gui/widgets/createwidget.h"
50
#include "gui/widgets/icon.h"
51
#include "gui/widgets/inttextfield.h"
52
#include "gui/widgets/itemcontainer.h"
53
#include "gui/widgets/itemlinkhandler.h"
54
#include "gui/widgets/layout.h"
55
#include "gui/widgets/extendedlistbox.h"
56
#include "gui/widgets/playerbox.h"
57
#include "gui/widgets/scrollarea.h"
58
59
#include "resources/npcdialoginfo.h"
60
61
#include "resources/db/avatardb.h"
62
#include "resources/db/npcdb.h"
63
#include "resources/db/npcdialogdb.h"
64
65
#include "resources/inventory/complexinventory.h"
66
67
#include "resources/item/complexitem.h"
68
69
#include "resources/loaders/imageloader.h"
70
71
#include "net/npchandler.h"
72
#include "net/packetlimiter.h"
73
74
#include "utils/copynpaste.h"
75
#include "utils/delete2.h"
76
#include "utils/foreach.h"
77
#include "utils/gettext.h"
78
79
#include <sstream>
80
81
#include "debug.h"
82
83
// TRANSLATORS: npc dialog button
84
#define CAPTION_WAITING _("Stop waiting")
85
// TRANSLATORS: npc dialog button
86
#define CAPTION_NEXT _("Next")
87
// TRANSLATORS: npc dialog button
88
#define CAPTION_CLOSE _("Close")
89
// TRANSLATORS: npc dialog button
90
#define CAPTION_SUBMIT _("Submit")
91
92
2
NpcDialog::DialogList NpcDialog::instances;
93
2
NpcDialogs NpcDialog::mNpcDialogs;
94
95
typedef STD_VECTOR<Image *>::iterator ImageVectorIter;
96
97
2
NpcDialog::NpcDialog(const BeingId npcId) :
98
    // TRANSLATORS: npc dialog name
99
2
    Window(_("NPC"), Modal_false, nullptr, "npc.xml"),
100
    ActionListener(),
101
    mNpcId(npcId),
102
    mDefaultInt(0),
103
    mDefaultString(),
104
    mTextBox(new BrowserBox(this, Opaque_true,
105

2
        "browserbox.xml")),
106
2
    mScrollArea(new ScrollArea(this, mTextBox,
107

8
        fromBool(getOptionBool("showtextbackground"), Opaque),
108

4
        "npc_textbackground.xml")),
109
    mText(),
110
    mNewText(),
111
    mItems(),
112
    mImages(),
113


8
    mItemList(CREATEWIDGETR(ExtendedListBox,
114
        this, this, "extendedlistbox.xml")),
115
2
    mListScrollArea(new ScrollArea(this, mItemList,
116

8
        fromBool(getOptionBool("showlistbackground"), Opaque),
117

4
        "npc_listbackground.xml")),
118

2
    mSkinContainer(new Container(this)),
119
2
    mSkinScrollArea(new ScrollArea(this, mSkinContainer,
120

8
        fromBool(getOptionBool("showlistbackground"), Opaque),
121

4
        "npc_listbackground.xml")),
122

2
    mItemLinkHandler(new ItemLinkHandler),
123

2
    mTextField(new TextField(this, "")),
124

2
    mIntField(new IntTextField(this)),
125
    // TRANSLATORS: npc dialog button
126

4
    mPlusButton(new Button(this, _("+"), "inc", this)),
127
    // TRANSLATORS: npc dialog button
128

4
    mMinusButton(new Button(this, _("-"), "dec", this)),
129
    // TRANSLATORS: npc dialog button
130

4
    mClearButton(new Button(this, _("Clear"), "clear", this)),
131

2
    mButton(new Button(this, "", "ok", this)),
132
    // TRANSLATORS: npc dialog button
133

4
    mButton2(new Button(this, _("Close"), "close", this)),
134
    // TRANSLATORS: npc dialog button
135

4
    mButton3(new Button(this, _("Add"), "add", this)),
136
    // TRANSLATORS: npc dialog button
137

4
    mResetButton(new Button(this, _("Reset"), "reset", this)),
138

2
    mInventory(new Inventory(InventoryType::Npc, 1)),
139

2
    mComplexInventory(new ComplexInventory(InventoryType::Craft, 1)),
140
    mItemContainer(new ItemContainer(this, mInventory,
141

2
        10000, ShowEmptyRows_true)),
142
2
    mItemScrollArea(new ScrollArea(this, mItemContainer,
143

8
        fromBool(getOptionBool("showitemsbackground"), Opaque),
144

4
        "npc_listbackground.xml")),
145
    mInputState(NpcInputState::NONE),
146
    mActionState(NpcActionState::WAIT),
147
    mSkinControls(),
148
    mSkinName(),
149

2
    mPlayerBox(new PlayerBox(nullptr)),
150
    mAvatarBeing(nullptr),
151
    mDialogInfo(nullptr),
152
    mLastNextTime(0),
153
    mCameraMode(-1),
154
    mCameraX(0),
155
    mCameraY(0),
156
    mShowAvatar(false),
157














232
    mLogInteraction(config.getBoolValue("logNpcInGui"))
158
{
159
    // Basic Window Setup
160
10
    setWindowName("NpcText");
161
2
    setResizable(true);
162
2
    setFocusable(true);
163
2
    setStickyButtonLock(true);
164
165
2
    setMinWidth(200);
166
2
    setMinHeight(150);
167
168
2
    setDefaultSize(300, 578, ImagePosition::LOWER_LEFT);
169
170
2
    mPlayerBox->setWidth(70);
171
2
    mPlayerBox->setHeight(100);
172
173
    // Setup output text box
174
4
    mTextBox->setOpaque(Opaque_false);
175

10
    mTextBox->setMaxRow(config.getIntValue("ChatLogLength"));
176
2
    mTextBox->setLinkHandler(mItemLinkHandler);
177
4
    mTextBox->setProcessVars(true);
178
2
    mTextBox->setFont(gui->getNpcFont());
179
4
    mTextBox->setEnableKeys(true);
180
4
    mTextBox->setEnableTabs(true);
181
4
    mTextBox->setEnableImages(true);
182
183
2
    mScrollArea->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
184
2
    mScrollArea->setVerticalScrollPolicy(ScrollArea::SHOW_ALWAYS);
185
186
    // Setup listbox
187
4
    mItemList->setWrappingEnabled(true);
188
10
    mItemList->setActionEventId("ok");
189
2
    mItemList->addActionListener(this);
190
4
    mItemList->setDistributeMousePressed(false);
191
4
    mItemList->setFont(gui->getNpcFont());
192

4
    if (gui->getNpcFont()->getHeight() < 20)
193
2
        mItemList->setRowHeight(20);
194
    else
195
        mItemList->setRowHeight(gui->getNpcFont()->getHeight());
196
197
2
    setContentSize(260, 175);
198
2
    mListScrollArea->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
199
2
    mItemScrollArea->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
200
2
    mSkinScrollArea->setScrollPolicy(ScrollArea::SHOW_NEVER,
201
        ScrollArea::SHOW_NEVER);
202
2
    mItemList->setVisible(Visible_true);
203
2
    mTextField->setVisible(Visible_true);
204
2
    mIntField->setVisible(Visible_true);
205
206
2
    const Font *const fnt = mButton->getFont();
207

12
    int width = std::max(fnt->getWidth(CAPTION_WAITING),
208

14
        fnt->getWidth(CAPTION_NEXT));
209

10
    width = std::max(width, fnt->getWidth(CAPTION_CLOSE));
210

10
    width = std::max(width, fnt->getWidth(CAPTION_SUBMIT));
211
2
    mButton->setWidth(8 + width);
212
213
    // Place widgets
214
2
    buildLayout();
215
216
2
    center();
217
2
    loadWindowState();
218
219
4
    instances.push_back(this);
220
2
}
221
222
2
void NpcDialog::postInit()
223
{
224
2
    Window::postInit();
225
2
    setVisible(Visible_true);
226
2
    requestFocus();
227
4
    enableVisibleSound(true);
228
2
    soundManager.playGuiSound(SOUND_SHOW_WINDOW);
229
230
2
    if (actorManager != nullptr)
231
    {
232
        const Being *const being = actorManager->findBeing(mNpcId);
233
        if (being != nullptr)
234
        {
235
            showAvatar(NPCDB::getAvatarFor(fromInt(
236
                being->getSubType(), BeingTypeId)));
237
            setCaption(being->getName());
238
        }
239
    }
240
241

8
    config.addListener("logNpcInGui", this);
242
2
}
243
244
24
NpcDialog::~NpcDialog()
245
{
246
2
    config.removeListeners(this);
247
    CHECKLISTENERS
248
2
    clearLayout();
249
250
2
    if (mPlayerBox != nullptr)
251
    {
252
4
        delete mPlayerBox->getBeing();
253
2
        delete mPlayerBox;
254
    }
255
256
2
    deleteSkinControls();
257
258
2
    delete2(mTextBox);
259
2
    delete2(mClearButton);
260
2
    delete2(mButton);
261
2
    delete2(mButton2);
262
2
    delete2(mButton3);
263
2
    delete2(mScrollArea);
264
2
    delete2(mItemList);
265
2
    delete2(mTextField);
266
2
    delete2(mIntField);
267
2
    delete2(mResetButton);
268
2
    delete2(mPlusButton);
269
2
    delete2(mMinusButton);
270
2
    delete2(mItemLinkHandler);
271
2
    delete2(mItemContainer);
272
2
    delete2(mInventory);
273
2
    delete2(mComplexInventory);
274
2
    delete2(mItemScrollArea);
275
2
    delete2(mListScrollArea);
276
2
    delete2(mSkinScrollArea);
277
278
8
    FOR_EACH (ImageVectorIter, it, mImages)
279
    {
280
        if (*it != nullptr)
281
            (*it)->decRef();
282
    }
283
284
4
    mImages.clear();
285
286
2
    instances.remove(this);
287
4
}
288
289
void NpcDialog::addText(const std::string &text, const bool save)
290
{
291
    if (save || mLogInteraction)
292
    {
293
        if (mText.size() > 5000)
294
            mText.clear();
295
296
        mNewText.append(text);
297
        mTextBox->addRow(text);
298
    }
299
    mScrollArea->setVerticalScrollAmount(mScrollArea->getVerticalMaxScroll());
300
    mActionState = NpcActionState::WAIT;
301
    buildLayout();
302
}
303
304
void NpcDialog::showNextButton()
305
{
306
    mActionState = NpcActionState::NEXT;
307
    buildLayout();
308
}
309
310
void NpcDialog::showCloseButton()
311
{
312
    mActionState = NpcActionState::CLOSE;
313
    buildLayout();
314
}
315
316
void NpcDialog::action(const ActionEvent &event)
317
{
318
    const std::string &eventId = event.getId();
319
    if (eventId == "ok")
320
    {
321
        if (mActionState == NpcActionState::NEXT)
322
        {
323
            if (!PacketLimiter::limitPackets(PacketType::PACKET_NPC_NEXT))
324
                return;
325
326
            nextDialog();
327
            addText(std::string(), false);
328
        }
329
        else if (mActionState == NpcActionState::CLOSE
330
                 || mActionState == NpcActionState::WAIT)
331
        {
332
            if (cutInWindow != nullptr)
333
                cutInWindow->hide();
334
            closeDialog();
335
        }
336
        else if (mActionState == NpcActionState::INPUT)
337
        {
338
            std::string printText;  // Text that will get printed
339
                                    // in the textbox
340
            switch (mInputState)
341
            {
342
                case NpcInputState::LIST:
343
                {
344
                    if (mDialogInfo != nullptr)
345
                        return;
346
                    if (gui != nullptr)
347
                        gui->resetClickCount();
348
                    const int selectedIndex = mItemList->getSelected();
349
350
                    if (selectedIndex >= CAST_S32(mItems.size())
351
                        || selectedIndex < 0
352
                        || !PacketLimiter::limitPackets(
353
                        PacketType::PACKET_NPC_INPUT))
354
                    {
355
                        return;
356
                    }
357
                    unsigned char choice = CAST_U8(
358
                        selectedIndex + 1);
359
                    printText = mItems[selectedIndex];
360
361
                    npcHandler->listInput(mNpcId, choice);
362
                    break;
363
                }
364
                case NpcInputState::STRING:
365
                {
366
                    if (!PacketLimiter::limitPackets(
367
                        PacketType::PACKET_NPC_INPUT))
368
                    {
369
                        return;
370
                    }
371
                    printText = mTextField->getText();
372
                    npcHandler->stringInput(mNpcId, printText);
373
                    break;
374
                }
375
                case NpcInputState::INTEGER:
376
                {
377
                    if (!PacketLimiter::limitPackets(
378
                        PacketType::PACKET_NPC_INPUT))
379
                    {
380
                        return;
381
                    }
382
                    printText = strprintf("%d", mIntField->getValue());
383
                    npcHandler->integerInput(
384
                        mNpcId, mIntField->getValue());
385
                    break;
386
                }
387
                case NpcInputState::ITEM:
388
                {
389
                    restoreVirtuals();
390
                    if (!PacketLimiter::limitPackets(
391
                        PacketType::PACKET_NPC_INPUT))
392
                    {
393
                        return;
394
                    }
395
396
                    std::string str;
397
                    const int sz = mInventory->getSize();
398
                    if (sz == 0)
399
                    {
400
                        str = "0,0";
401
                    }
402
                    else
403
                    {
404
                        const Item *item = mInventory->getItem(0);
405
                        if (item != nullptr)
406
                        {
407
                            str = strprintf("%d,%d", item->getId(),
408
                                toInt(item->getColor(), int));
409
                        }
410
                        else
411
                        {
412
                            str = "0,0";
413
                        }
414
                        for (int f = 1; f < sz; f ++)
415
                        {
416
                            str.append(";");
417
                            item = mInventory->getItem(f);
418
                            if (item != nullptr)
419
                            {
420
                                str.append(strprintf("%d,%d", item->getId(),
421
                                    toInt(item->getColor(), int)));
422
                            }
423
                            else
424
                            {
425
                                str.append("0,0");
426
                            }
427
                        }
428
                    }
429
430
                    // need send selected item
431
                    npcHandler->stringInput(mNpcId, str);
432
                    mInventory->clear();
433
                    break;
434
                }
435
                case NpcInputState::ITEM_INDEX:
436
                {
437
                    restoreVirtuals();
438
                    if (!PacketLimiter::limitPackets(
439
                        PacketType::PACKET_NPC_INPUT))
440
                    {
441
                        return;
442
                    }
443
444
                    std::string str;
445
                    const int sz = mInventory->getSize();
446
                    if (sz == 0)
447
                    {
448
                        str = "-1";
449
                    }
450
                    else
451
                    {
452
                        const Item *item = mInventory->getItem(0);
453
                        if (item != nullptr)
454
                        {
455
                            str = strprintf("%d", item->getTag());
456
                        }
457
                        else
458
                        {
459
                            str = "-1";
460
                        }
461
                        for (int f = 1; f < sz; f ++)
462
                        {
463
                            str.append(";");
464
                            item = mInventory->getItem(f);
465
                            if (item != nullptr)
466
                                str.append(strprintf("%d", item->getTag()));
467
                            else
468
                                str.append("-1");
469
                        }
470
                    }
471
472
                    // need send selected item
473
                    npcHandler->stringInput(mNpcId, str);
474
                    mInventory->clear();
475
                    break;
476
                }
477
                case NpcInputState::ITEM_CRAFT:
478
                {
479
                    restoreVirtuals();
480
                    if (!PacketLimiter::limitPackets(
481
                        PacketType::PACKET_NPC_INPUT))
482
                    {
483
                        return;
484
                    }
485
486
                    std::string str;
487
                    const int sz = mComplexInventory->getSize();
488
                    if (sz == 0)
489
                    {
490
                        str.clear();
491
                    }
492
                    else
493
                    {
494
                        const ComplexItem *item = dynamic_cast<ComplexItem*>(
495
                            mComplexInventory->getItem(0));
496
                        str = complexItemToStr(item);
497
                        for (int f = 1; f < sz; f ++)
498
                        {
499
                            str.append("|");
500
                            item = dynamic_cast<ComplexItem*>(
501
                                mComplexInventory->getItem(f));
502
                            str.append(complexItemToStr(item));
503
                        }
504
                    }
505
506
                    // need send selected item
507
                    npcHandler->stringInput(mNpcId, str);
508
                    mInventory->clear();
509
                    break;
510
                }
511
512
                case NpcInputState::NONE:
513
                default:
514
                    break;
515
            }
516
            if (mInputState != NpcInputState::ITEM &&
517
                mInputState != NpcInputState::ITEM_INDEX &&
518
                mInputState != NpcInputState::ITEM_CRAFT)
519
            {
520
                // addText will auto remove the input layout
521
                addText(strprintf("> \"%s\"", printText.c_str()), false);
522
            }
523
            mNewText.clear();
524
        }
525
526
        if (!mLogInteraction)
527
            mTextBox->clearRows();
528
    }
529
    else if (eventId == "reset")
530
    {
531
        switch (mInputState)
532
        {
533
            case NpcInputState::STRING:
534
                mTextField->setText(mDefaultString);
535
                break;
536
            case NpcInputState::INTEGER:
537
                mIntField->setValue(mDefaultInt);
538
                break;
539
            case NpcInputState::ITEM:
540
            case NpcInputState::ITEM_INDEX:
541
                mInventory->clear();
542
                break;
543
            case NpcInputState::ITEM_CRAFT:
544
                mComplexInventory->clear();
545
                break;
546
            case NpcInputState::NONE:
547
            case NpcInputState::LIST:
548
            default:
549
                break;
550
        }
551
    }
552
    else if (eventId == "inc")
553
    {
554
        mIntField->setValue(mIntField->getValue() + 1);
555
    }
556
    else if (eventId == "dec")
557
    {
558
        mIntField->setValue(mIntField->getValue() - 1);
559
    }
560
    else if (eventId == "clear")
561
    {
562
        switch (mInputState)
563
        {
564
            case NpcInputState::ITEM:
565
            case NpcInputState::ITEM_INDEX:
566
                mInventory->clear();
567
                break;
568
            case NpcInputState::ITEM_CRAFT:
569
                mComplexInventory->clear();
570
                break;
571
            case NpcInputState::STRING:
572
            case NpcInputState::INTEGER:
573
            case NpcInputState::LIST:
574
            case NpcInputState::NONE:
575
            default:
576
                clearRows();
577
                break;
578
        }
579
    }
580
    else if (eventId == "close")
581
    {
582
        restoreVirtuals();
583
        if (mActionState == NpcActionState::INPUT)
584
        {
585
            switch (mInputState)
586
            {
587
                case NpcInputState::ITEM:
588
                    npcHandler->stringInput(mNpcId, "0,0");
589
                    break;
590
                case NpcInputState::ITEM_INDEX:
591
                    npcHandler->stringInput(mNpcId, "-1");
592
                    break;
593
                case NpcInputState::ITEM_CRAFT:
594
                    npcHandler->stringInput(mNpcId, "");
595
                    break;
596
                case NpcInputState::STRING:
597
                case NpcInputState::INTEGER:
598
                case NpcInputState::NONE:
599
                case NpcInputState::LIST:
600
                default:
601
                    npcHandler->listInput(mNpcId, 255);
602
                    break;
603
            }
604
            if (cutInWindow != nullptr)
605
                cutInWindow->hide();
606
            closeDialog();
607
        }
608
    }
609
    else if (eventId == "add")
610
    {
611
        if (inventoryWindow != nullptr)
612
        {
613
            Item *const item = inventoryWindow->getSelectedItem();
614
            Inventory *const inventory = PlayerInfo::getInventory();
615
            if (inventory != nullptr)
616
            {
617
                if (mInputState == NpcInputState::ITEM_CRAFT)
618
                {
619
                    if (mComplexInventory->addVirtualItem(item, -1, 1))
620
                        inventory->virtualRemove(item, 1);
621
                }
622
                else
623
                {
624
                    if (mInventory->addVirtualItem(item, -1, 1))
625
                        inventory->virtualRemove(item, 1);
626
                }
627
            }
628
        }
629
    }
630
    else if (eventId.find("skin_") == 0)
631
    {
632
        const std::string cmd = eventId.substr(5);
633
        std::string printText;
634
        int cnt = 0;
635
        FOR_EACH (StringVectCIter, it, mItems)
636
        {
637
            if (cmd == *it)
638
            {
639
                npcHandler->listInput(mNpcId, CAST_U8(cnt + 1));
640
                printText = mItems[cnt];
641
642
                if (mInputState != NpcInputState::ITEM &&
643
                    mInputState != NpcInputState::ITEM_INDEX &&
644
                    mInputState != NpcInputState::ITEM_CRAFT)
645
                {
646
                    // addText will auto remove the input layout
647
                    addText(strprintf("> \"%s\"", printText.c_str()), false);
648
                }
649
                mNewText.clear();
650
                break;
651
            }
652
            cnt ++;
653
        }
654
    }
655
}
656
657
void NpcDialog::nextDialog()
658
{
659
    npcHandler->nextDialog(mNpcId);
660
}
661
662
void NpcDialog::closeDialog()
663
{
664
    restoreCamera();
665
    npcHandler->closeDialog(mNpcId);
666
}
667
668
2
int NpcDialog::getNumberOfElements()
669
{
670
4
    return CAST_S32(mItems.size());
671
}
672
673
std::string NpcDialog::getElementAt(int i)
674
{
675
    return mItems[i];
676
}
677
678
const Image *NpcDialog::getImageAt(int i)
679
{
680
    return mImages[i];
681
}
682
683
void NpcDialog::choiceRequest()
684
{
685
    mItems.clear();
686
    FOR_EACH (ImageVectorIter, it, mImages)
687
    {
688
        if (*it != nullptr)
689
            (*it)->decRef();
690
    }
691
    mImages.clear();
692
    mActionState = NpcActionState::INPUT;
693
    mInputState = NpcInputState::LIST;
694
    buildLayout();
695
}
696
697
void NpcDialog::addChoice(const std::string &choice)
698
{
699
    mItems.push_back(choice);
700
    mImages.push_back(nullptr);
701
}
702
703
void NpcDialog::parseListItems(const std::string &itemString)
704
{
705
    std::istringstream iss(itemString);
706
    std::string tmp;
707
    const std::string path = paths.getStringValue("guiIcons");
708
    while (getline(iss, tmp, ':'))
709
    {
710
        if (tmp.empty())
711
            continue;
712
        const size_t pos = tmp.find('|');
713
        if (pos == std::string::npos)
714
        {
715
            mItems.push_back(tmp);
716
            mImages.push_back(nullptr);
717
        }
718
        else
719
        {
720
            mItems.push_back(tmp.substr(pos + 1));
721
            Image *const img = Loader::getImage(pathJoin(path,
722
                std::string(tmp.substr(0, pos)).append(".png")));
723
            mImages.push_back(img);
724
        }
725
    }
726
727
    if (!mItems.empty())
728
    {
729
        mItemList->setSelected(0);
730
        mItemList->requestFocus();
731
    }
732
    else
733
    {
734
        mItemList->setSelected(-1);
735
    }
736
}
737
738
void NpcDialog::refocus()
739
{
740
    if (!mItems.empty())
741
        mItemList->refocus();
742
}
743
744
void NpcDialog::textRequest(const std::string &defaultText)
745
{
746
    mActionState = NpcActionState::INPUT;
747
    mInputState = NpcInputState::STRING;
748
    mDefaultString = defaultText;
749
    mTextField->setText(defaultText);
750
751
    buildLayout();
752
}
753
754
bool NpcDialog::isTextInputFocused() const
755
{
756
    return mTextField->isFocused();
757
}
758
759
bool NpcDialog::isInputFocused() const
760
{
761
    return mTextField->isFocused() || mIntField->isFocused()
762
        || mItemList->isFocused();
763
}
764
765
bool NpcDialog::isAnyInputFocused()
766
{
767
    FOR_EACH (DialogList::const_iterator, it, instances)
768
    {
769
        if (((*it) != nullptr) && (*it)->isInputFocused())
770
            return true;
771
    }
772
773
    return false;
774
}
775
776
void NpcDialog::integerRequest(const int defaultValue, const int min,
777
                               const int max)
778
{
779
    mActionState = NpcActionState::INPUT;
780
    mInputState = NpcInputState::INTEGER;
781
    mDefaultInt = defaultValue;
782
    mIntField->setRange(min, max);
783
    mIntField->setValue(defaultValue);
784
    buildLayout();
785
}
786
787
void NpcDialog::itemRequest(const int size)
788
{
789
    mActionState = NpcActionState::INPUT;
790
    mInputState = NpcInputState::ITEM;
791
    mInventory->resize(size);
792
    buildLayout();
793
}
794
795
void NpcDialog::itemIndexRequest(const int size)
796
{
797
    mActionState = NpcActionState::INPUT;
798
    mInputState = NpcInputState::ITEM_INDEX;
799
    mInventory->resize(size);
800
    buildLayout();
801
}
802
803
void NpcDialog::itemCraftRequest(const int size)
804
{
805
    mActionState = NpcActionState::INPUT;
806
    mInputState = NpcInputState::ITEM_CRAFT;
807
    mComplexInventory->resize(size);
808
    buildLayout();
809
}
810
811
void NpcDialog::move(const int amount)
812
{
813
    if (mActionState != NpcActionState::INPUT)
814
        return;
815
816
    switch (mInputState)
817
    {
818
        case NpcInputState::INTEGER:
819
            mIntField->setValue(mIntField->getValue() + amount);
820
            break;
821
        case NpcInputState::LIST:
822
            mItemList->setSelected(mItemList->getSelected() - amount);
823
            break;
824
        case NpcInputState::NONE:
825
        case NpcInputState::STRING:
826
        case NpcInputState::ITEM:
827
        case NpcInputState::ITEM_INDEX:
828
        case NpcInputState::ITEM_CRAFT:
829
        default:
830
            break;
831
    }
832
}
833
834
void NpcDialog::setVisible(Visible visible)
835
{
836
2
    Window::setVisible(visible);
837
838
    if (visible == Visible_false)
839
        scheduleDelete();
840
}
841
842
void NpcDialog::optionChanged(const std::string &name)
843
{
844
    if (name == "logNpcInGui")
845
        mLogInteraction = config.getBoolValue("logNpcInGui");
846
}
847
848
NpcDialog *NpcDialog::getActive()
849
{
850
    if (instances.size() == 1)
851
        return instances.front();
852
853
    FOR_EACH (DialogList::const_iterator, it, instances)
854
    {
855
        if (((*it) != nullptr) && (*it)->isFocused())
856
            return (*it);
857
    }
858
859
    return nullptr;
860
}
861
862
void NpcDialog::closeAll()
863
{
864
    FOR_EACH (DialogList::const_iterator, it, instances)
865
    {
866
        if (*it != nullptr)
867
            (*it)->close();
868
    }
869
}
870
871
2
void NpcDialog::placeNormalControls()
872
{
873
2
    if (mShowAvatar)
874
    {
875
        place(0, 0, mPlayerBox);
876
        place(1, 0, mScrollArea, 5, 3);
877
        place(4, 3, mClearButton);
878
        place(5, 3, mButton);
879
    }
880
    else
881
    {
882
2
        place(0, 0, mScrollArea, 5, 3);
883
2
        place(3, 3, mClearButton);
884
2
        place(4, 3, mButton);
885
    }
886
2
}
887
888
void NpcDialog::placeMenuControls()
889
{
890
    if (mShowAvatar)
891
    {
892
        place(0, 0, mPlayerBox);
893
        place(1, 0, mScrollArea, 6, 3);
894
        place(0, 3, mListScrollArea, 7, 3);
895
        place(1, 6, mButton2, 2);
896
        place(3, 6, mClearButton, 2);
897
        place(5, 6, mButton, 2);
898
    }
899
    else
900
    {
901
        place(0, 0, mScrollArea, 6, 3);
902
        place(0, 3, mListScrollArea, 6, 3);
903
        place(0, 6, mButton2, 2);
904
        place(2, 6, mClearButton, 2);
905
        place(4, 6, mButton, 2);
906
    }
907
}
908
909
void NpcDialog::placeSkinControls()
910
{
911
    createSkinControls();
912
    if ((mDialogInfo != nullptr) && mDialogInfo->hideText)
913
    {
914
        if (mShowAvatar)
915
        {
916
            place(0, 0, mPlayerBox);
917
            place(1, 0, mSkinScrollArea, 7, 3);
918
            place(1, 3, mButton2, 2);
919
        }
920
        else
921
        {
922
            place(0, 0, mSkinScrollArea, 6, 3);
923
            place(0, 3, mButton2, 2);
924
        }
925
    }
926
    else
927
    {
928
        if (mShowAvatar)
929
        {
930
            place(0, 0, mPlayerBox);
931
            place(1, 0, mScrollArea, 6, 3);
932
            place(0, 3, mSkinScrollArea, 7, 3);
933
            place(1, 6, mButton2, 2);
934
        }
935
        else
936
        {
937
            place(0, 0, mScrollArea, 6, 3);
938
            place(0, 3, mSkinScrollArea, 6, 3);
939
            place(0, 6, mButton2, 2);
940
        }
941
    }
942
}
943
944
void NpcDialog::placeTextInputControls()
945
{
946
    if (mShowAvatar)
947
    {
948
        place(0, 0, mPlayerBox);
949
        place(1, 0, mScrollArea, 6, 3);
950
        place(1, 3, mTextField, 6);
951
        place(1, 4, mResetButton, 2);
952
        place(3, 4, mClearButton, 2);
953
        place(5, 4, mButton, 2);
954
    }
955
    else
956
    {
957
        place(0, 0, mScrollArea, 6, 3);
958
        place(0, 3, mTextField, 6);
959
        place(0, 4, mResetButton, 2);
960
        place(2, 4, mClearButton, 2);
961
        place(4, 4, mButton, 2);
962
    }
963
}
964
965
void NpcDialog::placeIntInputControls()
966
{
967
    if (mShowAvatar)
968
    {
969
        place(0, 0, mPlayerBox);
970
        place(1, 0, mScrollArea, 6, 3);
971
        place(1, 3, mMinusButton, 1);
972
        place(2, 3, mIntField, 4);
973
        place(6, 3, mPlusButton, 1);
974
        place(1, 4, mResetButton, 2);
975
        place(3, 4, mClearButton, 2);
976
        place(5, 4, mButton, 2);
977
    }
978
    else
979
    {
980
        place(0, 0, mScrollArea, 6, 3);
981
        place(0, 3, mMinusButton, 1);
982
        place(1, 3, mIntField, 4);
983
        place(5, 3, mPlusButton, 1);
984
        place(0, 4, mResetButton, 2);
985
        place(2, 4, mClearButton, 2);
986
        place(4, 4, mButton, 2);
987
    }
988
}
989
990
void NpcDialog::placeItemInputControls()
991
{
992
    if (mDialogInfo != nullptr)
993
    {
994
        mItemContainer->setCellBackgroundImage(mDialogInfo->inventory.cell);
995
        mItemContainer->setMaxColumns(mDialogInfo->inventory.columns);
996
    }
997
    else
998
    {
999
        mItemContainer->setCellBackgroundImage("inventory_cell.xml");
1000
        mItemContainer->setMaxColumns(10000);
1001
    }
1002
1003
    if (mInputState == NpcInputState::ITEM_CRAFT)
1004
        mItemContainer->setInventory(mComplexInventory);
1005
    else
1006
        mItemContainer->setInventory(mInventory);
1007
1008
    if ((mDialogInfo != nullptr) && mDialogInfo->hideText)
1009
    {
1010
        if (mShowAvatar)
1011
        {
1012
            place(0, 0, mPlayerBox);
1013
            place(1, 0, mItemScrollArea, 7, 3);
1014
            place(1, 3, mButton3, 2);
1015
            place(3, 3, mClearButton, 2);
1016
            place(5, 3, mButton, 2);
1017
        }
1018
        else
1019
        {
1020
            place(0, 0, mItemScrollArea, 6, 3);
1021
            place(0, 3, mButton3, 2);
1022
            place(2, 3, mClearButton, 2);
1023
            place(4, 3, mButton, 2);
1024
        }
1025
    }
1026
    else
1027
    {
1028
        if (mShowAvatar)
1029
        {
1030
            place(0, 0, mPlayerBox);
1031
            place(1, 0, mScrollArea, 6, 3);
1032
            place(0, 3, mItemScrollArea, 7, 3);
1033
            place(1, 6, mButton3, 2);
1034
            place(3, 6, mClearButton, 2);
1035
            place(5, 6, mButton, 2);
1036
        }
1037
        else
1038
        {
1039
            place(0, 0, mScrollArea, 6, 3);
1040
            place(0, 3, mItemScrollArea, 6, 3);
1041
            place(0, 6, mButton3, 2);
1042
            place(2, 6, mClearButton, 2);
1043
            place(4, 6, mButton, 2);
1044
        }
1045
    }
1046
}
1047
1048
2
void NpcDialog::buildLayout()
1049
{
1050
2
    clearLayout();
1051
1052
2
    if (mActionState != NpcActionState::INPUT)
1053
    {
1054
2
        if (mActionState == NpcActionState::WAIT)
1055
10
            mButton->setCaption(CAPTION_WAITING);
1056
        else if (mActionState == NpcActionState::NEXT)
1057
            mButton->setCaption(CAPTION_NEXT);
1058
        else if (mActionState == NpcActionState::CLOSE)
1059
            mButton->setCaption(CAPTION_CLOSE);
1060
2
        placeNormalControls();
1061
    }
1062
    else if (mInputState != NpcInputState::NONE)
1063
    {
1064
        mButton->setCaption(CAPTION_SUBMIT);
1065
        switch (mInputState)
1066
        {
1067
            case NpcInputState::LIST:
1068
                if (mDialogInfo == nullptr)
1069
                    placeMenuControls();
1070
                else
1071
                    placeSkinControls();
1072
                mItemList->setSelected(-1);
1073
                break;
1074
1075
            case NpcInputState::STRING:
1076
                placeTextInputControls();
1077
                break;
1078
1079
            case NpcInputState::INTEGER:
1080
                placeIntInputControls();
1081
                break;
1082
1083
            case NpcInputState::ITEM:
1084
            case NpcInputState::ITEM_INDEX:
1085
            case NpcInputState::ITEM_CRAFT:
1086
                placeItemInputControls();
1087
                break;
1088
1089
            case NpcInputState::NONE:
1090
            default:
1091
                break;
1092
        }
1093
    }
1094
1095
2
    Layout &layout = getLayout();
1096
2
    layout.setRowHeight(1, LayoutType::SET);
1097
2
    redraw();
1098
2
    mScrollArea->setVerticalScrollAmount(mScrollArea->getVerticalMaxScroll());
1099
2
}
1100
1101
void NpcDialog::saveCamera()
1102
{
1103
    if ((viewport == nullptr) || mCameraMode >= 0)
1104
        return;
1105
1106
    mCameraMode = CAST_S32(settings.cameraMode);
1107
    mCameraX = viewport->getCameraRelativeX();
1108
    mCameraY = viewport->getCameraRelativeY();
1109
}
1110
1111
void NpcDialog::restoreCamera()
1112
{
1113
    if ((viewport == nullptr) || mCameraMode == -1)
1114
        return;
1115
1116
    if (CAST_S32(settings.cameraMode) != mCameraMode)
1117
        viewport->toggleCameraMode();
1118
    if (mCameraMode != 0)
1119
    {
1120
        viewport->setCameraRelativeX(mCameraX);
1121
        viewport->setCameraRelativeY(mCameraY);
1122
    }
1123
    mCameraMode = -1;
1124
}
1125
1126
void NpcDialog::showAvatar(const BeingTypeId avatarId)
1127
{
1128
    const bool needShow = (avatarId != BeingTypeId_zero);
1129
    if (needShow)
1130
    {
1131
        delete mAvatarBeing;
1132
        mAvatarBeing = Being::createBeing(BeingId_zero,
1133
            ActorType::Avatar,
1134
            avatarId,
1135
            nullptr);
1136
        mPlayerBox->setPlayer(mAvatarBeing);
1137
        if (!mAvatarBeing->mSprites.empty())
1138
        {
1139
            mAvatarBeing->logic();
1140
            const BeingInfo *const info = AvatarDB::get(avatarId);
1141
            const int pad2 = 2 * mPadding;
1142
            int width = 0;
1143
            if (info != nullptr)
1144
            {
1145
                width = info->getWidth();
1146
                mPlayerBox->setWidth(width + pad2);
1147
                mPlayerBox->setHeight(info->getHeight() + pad2);
1148
            }
1149
            const Sprite *const sprite = mAvatarBeing->mSprites[0];
1150
            if ((sprite != nullptr) && (width == 0))
1151
            {
1152
                mPlayerBox->setWidth(sprite->getWidth() + pad2);
1153
                mPlayerBox->setHeight(sprite->getHeight() + pad2);
1154
            }
1155
        }
1156
    }
1157
    else
1158
    {
1159
        delete2(mAvatarBeing)
1160
        mPlayerBox->setPlayer(nullptr);
1161
    }
1162
    if (needShow != mShowAvatar)
1163
    {
1164
        mShowAvatar = needShow;
1165
        buildLayout();
1166
    }
1167
    else
1168
    {
1169
        mShowAvatar = needShow;
1170
    }
1171
}
1172
1173
void NpcDialog::setAvatarDirection(const uint8_t direction)
1174
{
1175
    Being *const being = mPlayerBox->getBeing();
1176
    if (being != nullptr)
1177
        being->setDirection(direction);
1178
}
1179
1180
void NpcDialog::setAvatarAction(const int actionId)
1181
{
1182
    Being *const being = mPlayerBox->getBeing();
1183
    if (being != nullptr)
1184
        being->setAction(static_cast<BeingActionT>(actionId), 0);
1185
}
1186
1187
void NpcDialog::logic()
1188
{
1189
    BLOCK_START("NpcDialog::logic")
1190
    Window::logic();
1191
    if (mShowAvatar && (mAvatarBeing != nullptr))
1192
    {
1193
        mAvatarBeing->logic();
1194
        if (mPlayerBox->getWidth() < CAST_S32(3 * getPadding()))
1195
        {
1196
            const Sprite *const sprite = mAvatarBeing->mSprites[0];
1197
            if (sprite != nullptr)
1198
            {
1199
                mPlayerBox->setWidth(sprite->getWidth() + 2 * getPadding());
1200
                mPlayerBox->setHeight(sprite->getHeight() + 2 * getPadding());
1201
                buildLayout();
1202
            }
1203
        }
1204
    }
1205
    BLOCK_END("NpcDialog::logic")
1206
}
1207
1208
void NpcDialog::clearRows()
1209
{
1210
    mTextBox->clearRows();
1211
}
1212
1213
void NpcDialog::clearDialogs()
1214
{
1215
    NpcDialogs::iterator it = mNpcDialogs.begin();
1216
    const NpcDialogs::iterator it_end = mNpcDialogs.end();
1217
    while (it != it_end)
1218
    {
1219
        delete (*it).second;
1220
        ++ it;
1221
    }
1222
    mNpcDialogs.clear();
1223
}
1224
1225
void NpcDialog::mousePressed(MouseEvent &event)
1226
{
1227
    Window::mousePressed(event);
1228
    if (event.getButton() == MouseButton::RIGHT
1229
        && event.getSource() == mTextBox)
1230
    {
1231
        event.consume();
1232
        if (popupMenu != nullptr)
1233
        {
1234
            popupMenu->showNpcDialogPopup(mNpcId,
1235
                viewport->mMouseX,
1236
                viewport->mMouseY);
1237
        }
1238
    }
1239
}
1240
1241
void NpcDialog::copyToClipboard(const int x, const int y) const
1242
{
1243
    std::string str = mTextBox->getTextAtPos(x, y);
1244
    sendBuffer(str);
1245
}
1246
1247
void NpcDialog::setSkin(const std::string &skin)
1248
{
1249
    if (skin.empty())
1250
    {
1251
        mSkinName = skin;
1252
        mDialogInfo = nullptr;
1253
        return;
1254
    }
1255
    const NpcDialogInfo *const dialog = NpcDialogDB::getDialog(skin);
1256
    if (dialog == nullptr)
1257
    {
1258
        logger->log("Error: creating controls for not existing npc dialog %s",
1259
            skin.c_str());
1260
        return;
1261
    }
1262
    mSkinName = skin;
1263
    mDialogInfo = dialog;
1264
}
1265
1266
void NpcDialog::deleteSkinControls()
1267
{
1268
2
    mSkinContainer->removeControls();
1269
}
1270
1271
void NpcDialog::createSkinControls()
1272
{
1273
    deleteSkinControls();
1274
1275
    if (mDialogInfo == nullptr)
1276
        return;
1277
1278
    FOR_EACH (STD_VECTOR<NpcImageInfo*>::const_iterator,
1279
        it,
1280
        mDialogInfo->menu.images)
1281
    {
1282
        const NpcImageInfo *const info = *it;
1283
        Image *const image = Theme::getImageFromTheme(info->name);
1284
        if (image != nullptr)
1285
        {
1286
            Icon *const icon = new Icon(this, image, AutoRelease_true);
1287
            icon->setPosition(info->x, info->y);
1288
            mSkinContainer->add(icon);
1289
        }
1290
    }
1291
    FOR_EACH (STD_VECTOR<NpcTextInfo*>::const_iterator,
1292
        it,
1293
        mDialogInfo->menu.texts)
1294
    {
1295
        const NpcTextInfo *const info = *it;
1296
        BrowserBox *box = new BrowserBox(this,
1297
            Opaque_true,
1298
            "browserbox.xml");
1299
        box->setOpaque(Opaque_false);
1300
        box->setMaxRow(config.getIntValue("ChatLogLength"));
1301
        box->setLinkHandler(mItemLinkHandler);
1302
        box->setProcessVars(true);
1303
        box->setFont(gui->getNpcFont());
1304
        box->setEnableKeys(true);
1305
        box->setEnableTabs(true);
1306
        box->setPosition(info->x, info->y);
1307
        mSkinContainer->add(box);
1308
        box->setWidth(info->width);
1309
        box->setHeight(info->height);
1310
        StringVect parts;
1311
        splitToStringVector(parts, info->text, '\n');
1312
        FOR_EACH (StringVectCIter, it2, parts)
1313
        {
1314
            box->addRow(*it2);
1315
        }
1316
    }
1317
    FOR_EACH (STD_VECTOR<NpcButtonInfo*>::const_iterator,
1318
        it,
1319
        mDialogInfo->menu.buttons)
1320
    {
1321
        const NpcButtonInfo *const info = *it;
1322
        Button *const button = new Button(this);
1323
        button->setCaption(info->name);
1324
        button->setActionEventId("skin_" + info->value);
1325
        button->addActionListener(this);
1326
        button->setPosition(info->x, info->y);
1327
        if (!info->image.empty())
1328
        {
1329
            button->setImageWidth(info->imageWidth);
1330
            button->setImageHeight(info->imageHeight);
1331
            button->loadImageSet(info->image);
1332
        }
1333
        mSkinContainer->add(button);
1334
        button->adjustSize();
1335
    }
1336
}
1337
1338
void NpcDialog::restoreVirtuals()
1339
{
1340
    Inventory *const inventory = PlayerInfo::getInventory();
1341
    if (inventory != nullptr)
1342
        inventory->restoreVirtuals();
1343
}
1344
1345
std::string NpcDialog::complexItemToStr(const ComplexItem *const item)
1346
{
1347
    std::string str;
1348
    if (item != nullptr)
1349
    {
1350
        const STD_VECTOR<Item*> &items = item->getChilds();
1351
        const size_t sz = items.size();
1352
        if (sz == 0u)
1353
            return str;
1354
1355
        const Item *item2 = items[0];
1356
1357
        str = strprintf("%d,%d",
1358
            item2->getInvIndex(),
1359
            item2->getQuantity());
1360
        for (size_t f = 1; f < sz; f ++)
1361
        {
1362
            str.append(";");
1363
            item2 = items[f];
1364
            str.append(strprintf("%d,%d",
1365
                item2->getInvIndex(),
1366
                item2->getQuantity()));
1367
        }
1368
    }
1369
    else
1370
    {
1371
        str.clear();
1372
    }
1373
    return str;
1374
}
1375
1376
void NpcDialog::addCraftItem(Item *const item,
1377
                             const int amount,
1378
                             const int slot)
1379
{
1380
    if (mInputState != NpcInputState::ITEM_CRAFT)
1381
        return;
1382
1383
    Inventory *const inventory = PlayerInfo::getInventory();
1384
1385
    if (inventory == nullptr)
1386
        return;
1387
1388
    if (mComplexInventory->addVirtualItem(
1389
        item,
1390
        slot,
1391
        amount))
1392
    {
1393
        inventory->virtualRemove(item, amount);
1394
    }
1395

6
}