GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/windows/npcdialog.cpp Lines: 140 700 20.0 %
Date: 2018-05-19 03:07:18 Branches: 152 787 19.3 %

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-2018  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", false), Opaque),
108

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


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

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

4
        "npc_listbackground.xml")),
118

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

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

4
        "npc_listbackground.xml")),
122

2
    mItemLinkHandler(new ItemLinkHandler),
123
4
    mTextField(new TextField(this, std::string(), LoseFocusOnTab_true,
124

4
        nullptr, std::string(), false)),
125

2
    mIntField(new IntTextField(this, 0, 0, 0, Enable_true, 0)),
126
    // TRANSLATORS: npc dialog button
127

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

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

2
    mClearButton(new Button(this, _("Clear"), "clear", BUTTON_SKIN, this)),
132

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

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

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

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

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

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

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

8
        fromBool(getOptionBool("showitemsbackground", false), Opaque),
145

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

8
    mPlayerBox(new PlayerBox(nullptr, std::string(), std::string())),
151
    mAvatarBeing(nullptr),
152
    mDialogInfo(nullptr),
153
    mLastNextTime(0),
154
    mCameraMode(-1),
155
    mCameraX(0),
156
    mCameraY(0),
157
    mShowAvatar(false),
158












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

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

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

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

14
        fnt->getWidth(CAPTION_NEXT));
210

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

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

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

6
}